# Keenpix complete public knowledge collection > Generated from the same registry as each canonical page Markdown response. Canonical HTML remains authoritative for interactive behavior; dated primary sources remain authoritative for volatile external facts. Generated coverage: 100 public knowledge pages, including 9 structured comparisons. --- Markdown path: https://keenpix.com/index.md # Keenpix image optimization CDN > Keenpix is a developer-focused image optimization CDN available as managed cloud or an AGPL-3.0 self-hosted engine. Canonical HTML: [https://keenpix.com/](https://keenpix.com/) Last reviewed: 2026-09-02 ## What it does Keenpix transforms allowlisted source images, caches variants, and delivers responsive AVIF, WebP, JPEG, PNG, and other documented outputs. ## Choose a path - [Learn image delivery fundamentals](https://keenpix.com/learn) - [Start the cloud quickstart](https://keenpix.com/docs/getting-started/cloud-quickstart) - [Evaluate self-hosting](https://keenpix.com/self-hosted-image-cdn) - [Review pricing](https://keenpix.com/pricing) - [Read security boundaries](https://keenpix.com/security) ## Limitations Keenpix is not a DAM, source asset library, video platform, OAuth server, public API sandbox, or official CLI. Use the system that owns those requirements. --- Markdown path: https://keenpix.com/about.md # The honest image CDN > Keenpix is a focused image optimization CDN with managed delivery and an AGPL-3.0 self-hosted engine. Canonical HTML: [https://keenpix.com/about](https://keenpix.com/about) Last reviewed: 2026-09-02 ## Product principles - Bill managed customers for successful optimized delivery, not transforms or seats. - Keep the released engine open source and the source origin under customer control. - Publish limits, prices, dated comparisons, and corrections without invented proof. ## Product-fact sources The public repository, release history, documentation, changelog, trust pages, and dated comparison sources are the inspection surfaces. Missing metrics, certifications, and benchmarks remain unknown. ## Company and contact Source: [GitHub](https://github.com/lord007tn/keenpix). Contact: hi@raedbahri.com. --- Markdown path: https://keenpix.com/authors/raed-bahri.md # Raed Bahri > Author and maintainer profile for Keenpix technical and comparison content. Canonical HTML: [https://keenpix.com/authors/raed-bahri](https://keenpix.com/authors/raed-bahri) Author: Raed Bahri Last reviewed: 2026-09-02 Raed Bahri builds and maintains Keenpix. Technical claims are checked against current public code and documentation; volatile competitor facts use dated primary sources. Request corrections through [support](https://keenpix.com/support). --- Markdown path: https://keenpix.com/blog.md # Keenpix blog > Implementation guides, category education, and source-backed comparisons. Canonical HTML: [https://keenpix.com/blog](https://keenpix.com/blog) Last reviewed: 2026-09-03 - [Design a user-upload image pipeline end to end](https://keenpix.com/blog/user-upload-image-pipeline-design): Design image uploads from authenticated ingestion and byte validation through quarantine, moderation, durable storage, transformation, delivery, and recovery. - [Cache invalidation with versioned image URLs](https://keenpix.com/blog/cache-invalidation-versioned-image-urls): Use immutable versioned image URLs across browser, CDN, and origin caches, with safe publication, rollback, purge, revocation, and verification patterns. - [Safe image CDN rollouts and rollbacks](https://keenpix.com/blog/safe-image-cdn-rollouts-and-rollbacks): Canary image delivery changes with explicit acceptance gates, cache-aware rollback, observability, and a tested path back to the previous URL generator. - [How to measure image CDN performance reproducibly](https://keenpix.com/blog/reproducible-image-performance-measurement): Build a repeatable image delivery test that separates cold transforms, warm caches, transfer, decoding, rendering, and field experience. - [Private image origin security boundaries](https://keenpix.com/blog/private-image-origins-security-boundaries): Choose between public assets, signed source grants, authenticated gateways, and private networks without leaking origin credentials into delivery URLs. - [Image delivery troubleshooting: diagnose by symptom](https://keenpix.com/blog/image-delivery-troubleshooting-by-symptom): Trace broken, slow, stale, oversized, or incorrectly cropped images from browser selection through caches, transforms, and origin access. - [Bring-your-own-origin image CDN architecture](https://keenpix.com/blog/bring-your-own-origin-image-cdn-architecture): Design an image delivery layer around source assets you already own, with explicit origin, cache, URL, security, and recovery boundaries. - [Agent-assisted image CDN integration without blind trust](https://keenpix.com/blog/agent-assisted-image-cdn-integration): Give a coding agent public, bounded contracts for image delivery, then verify generated URLs, security boundaries, responsive behavior, and rollback. - [Secure image pipelines: SSRF to image bombs](https://keenpix.com/blog/secure-image-pipelines-ssrf-image-bombs): Defend image fetch and transform services with origin allowlists, redirect revalidation, byte and pixel ceilings, timeouts, and observable failures. - [Vercel Image Optimization to Keenpix Migration](https://keenpix.com/blog/migrate-vercel-image-optimization-to-keenpix): Move next/image from Vercel's optimizer to a Keenpix custom loader with config mapping, canary rollout, cache and origin checks, and rollback. - [ImageKit to Keenpix Migration Guide](https://keenpix.com/blog/migrate-imagekit-to-keenpix): Move ImageKit transforms and origins to Keenpix with a compatibility table, asset-export gate, canary plan, cache checks, and rollback. - [Cloudinary to Keenpix Migration Guide](https://keenpix.com/blog/migrate-cloudinary-to-keenpix): Migrate Cloudinary image URLs to Keenpix with an explicit parameter map, origin plan, canary rollout, cache checks, and tested rollback. - [Stop cache stampedes in image transformation](https://keenpix.com/blog/image-transform-cache-stampedes-capacity): Coalesce cold image transforms, serve stale safely, shed overload, and size CPU and memory capacity from measured service time and working sets. - [Image CDN cache keys: tame Vary: Accept](https://keenpix.com/blog/image-cdn-cache-keys-vary-accept): Design image cache keys that keep AVIF, WebP, and JPEG correct without multiplying variants for every raw Accept header or query permutation. - [Signed image URLs with HMAC: stop unauthorized transforms](https://keenpix.com/blog/signed-image-urls-hmac): Learn when an origin allowlist is enough, when HMAC signing helps, how Keenpix canonicalizes a transform request, and what secret rotation breaks. - [Self-host an image CDN with Docker: production checklist](https://keenpix.com/blog/self-host-image-cdn-docker): Deploy Keenpix with a pinned Docker image, persistent Postgres, health checks, CDN cache rules, backups, and a repeatable smoke test. - [How to optimize Next.js images with a custom CDN loader](https://keenpix.com/blog/nextjs-custom-image-cdn-loader): Connect next/image to Keenpix with a custom loader, responsive sizes, format negotiation, and an origin allowlist you can verify in the browser. - [AVIF vs WebP in production: choose with your cache](https://keenpix.com/blog/avif-vs-webp-production-caching): Compare AVIF and WebP without borrowed benchmarks, then configure format negotiation and CDN cache variants without serving the wrong bytes. - [How JoodCMS integrates Keenpix image delivery](https://keenpix.com/blog/joodcms-keenpix-integration): A code-backed case study of JoodCMS using Keenpix for responsive variants, source allowlists, domain sync, prewarming, and safe fallbacks. - [What is an image CDN? How optimization and delivery work](https://keenpix.com/blog/what-is-an-image-cdn): An image CDN fetches, transforms, caches, and delivers image variants close to users. Learn the request lifecycle, tradeoffs, and when you need one. - [Responsive image CDN guide: srcset, sizes, DPR, and formats](https://keenpix.com/blog/responsive-image-cdn-guide): Build responsive image CDN URLs with srcset and sizes, choose width candidates, handle DPR and format negotiation, and avoid common cache mistakes. - [Image CDN vs traditional CDN: caching and transforms](https://keenpix.com/blog/image-cdn-vs-traditional-cdn): A traditional CDN caches existing files. An image CDN creates optimized variants and then caches them. Compare responsibilities, costs, and use cases. - [Best Image CDNs in 2026: 10 Services Compared](https://keenpix.com/blog/best-image-cdns-2026): Compare 10 image CDN and optimization services using verified August 2026 pricing, use cases, tradeoffs, and clear best-fit recommendations for developers. - [Cloudinary Credits Explained: Pricing vs Keenpix](https://keenpix.com/blog/keenpix-vs-cloudinary): Learn how Cloudinary credits combine transformations, storage, and bandwidth, then compare that meter with Keenpix managed-delivery pricing. - [imgix Credits Explained: Delivery and Storage Pricing](https://keenpix.com/blog/keenpix-vs-imgix): Learn how imgix credit packages combine managed media and delivery allowances, then compare the meter with Keenpix managed-delivery pricing. - [ImageKit Bandwidth Pricing Explained: Limits and Overage](https://keenpix.com/blog/keenpix-vs-imagekit): Learn how ImageKit bandwidth allowances and overage work, then compare their boundary with Keenpix managed-delivery pricing. - [Image CDN Pricing: Why Keenpix Bills by Managed Delivery](https://keenpix.com/blog/transparent-image-cdn-pricing): See why Keenpix bills for optimized managed delivery instead of transformations, with published plans, included transforms, and always-on metered overage. - [Self-hosted vs managed image optimization: how to choose](https://keenpix.com/blog/self-host-vs-managed-image-optimization): Compare who owns the database, cache, delivery bill, upgrades, monitoring, and failure recovery in self-hosted and managed image optimization. --- Markdown path: https://keenpix.com/changelog.md # Keenpix product changelog > Every notable Keenpix release from the repository changelog. Canonical HTML: [https://keenpix.com/changelog](https://keenpix.com/changelog) # Changelog All notable changes to Keenpix are documented here. ## [v0.3.2] - 2026-09-01 ### Added - Added evidence-led comparison and migration content plus a measurable path from comparison pages to image activation. - Added the image CDN cost calculator and expanded machine-readable product and API discovery surfaces. ### Changed - Renamed and clarified the managed delivery edge runtime and its Cloudflare for SaaS ownership boundary. - Replaced the application theme bootstrap with the native TanStack Start provider and enabled the React Compiler. ### Fixed - Made the self-hosting deploy command derive its Git and image tags from the unified release version so the four services cannot drift independently. - Added generated required secrets to the self-hosting setup command and regression coverage for release and secret consistency. - Updated production runtime defaults to the immutable `v0.3.2` image set. ## [v0.3.1] - 2026-08-19 ### Changed - Unified the platform apps, SDK, and framework packages on one release version and one repository tag. - Package publication now runs only from a reviewed `vMAJOR.MINOR.PATCH` tag. - GitHub publishes one release whose notes are grouped by platform component and public package instead of creating a release for every package. ### Fixed - Added deterministic, idempotent npm publishing without package-specific Git tags. - Added repository metadata required for npm provenance across every public package. ## [v0.3.0] - 2026-08-18 ### Added - Bilingual English/Arabic blog routing, localized metadata, hreflang alternates, sitemap entries, and four paired production guides. - Durable managed-delivery metering with settled hourly windows, Cloudflare capture coverage checks, deletion-safe billing attribution, and fail-closed reporting. ### Changed - Managed framework helpers, SDK configuration, onboarding, and docs now emit only `cdn.keenpix.com/p//img/` URLs. Public managed delivery is no longer accepted from the application hostname. - New checkout and every public pricing fallback now expose only standard $9/$29/$69 monthly plans; historical founding products remain auditable but unlisted until allocation can be made race-safe. - Public-route bundles, accessibility landmarks, schema, comparison tables, security headers, email delivery validation, and production deployment guidance were hardened for launch. ### Fixed - Corrected signed-URL examples and encoding, draft indexability, Arabic document direction, trailing-slash/canonical behavior, 404 metadata, and sitemap localization. - Corrected the container entrypoint's migration and seed invocations and kept BullMQ/SVGO runtime dependencies outside relocated server bundles so fresh four-service Docker deployments can start and answer health checks. - Prevented usage watermarks from advancing across incomplete outbox drains, trial usage, missing edge coverage, or deleted-project attribution gaps. ## [v0.2.1] - 2026-08-05 ### Added - Founding-customer pricing and production catalog configuration. - Project-attributed edge analytics, billing-period filters, platform finance reporting, and admin impersonation for customer support. - A safe legacy edge-data backfill packaged with the production runtime. ### Changed - Pricing, cost projections, and production workflows now use the same founding-cohort assumptions and attributed delivery data. - Cloud image delivery URLs are canonicalized consistently across application and edge reporting paths. - Analytics delivery labels and finance telemetry now describe the reconciled edge, cache, and live-origin stages. ### Fixed - Tenant analytics no longer rely on shared edge placeholders or leak platform-level values into customer views. - Platform costs, successful-delivery rankings, request-attempt totals, and founding-cohort projections now use reconciled inputs. - Cloudflare account credentials are separated from project analytics credentials, and Analytics Engine queries use its supported SQL surface. - Runtime startup no longer depends on dotenv, and production images include the edge backfill required by the deployed revision. ## [v0.2.0] - 2026-07-13 ### Added - Managed cloud mode with multi-tenant organizations, self-serve signup, email verification, Google OAuth support, and an operator console. - Polar-backed trials, subscriptions, always-on usage metering, billing alerts, dunning handling, and organization-scoped billing recovery. - Consent-aware GTM/GA4 funnel events, a cookie-choice surface, and Cloudflare Web Analytics RUM for page performance and Core Web Vitals. - Public pricing, comparison, self-hosting, changelog, trust, security, status, support, author, and editorial-methodology pages. - A 10-article blog with focused image-CDN guides, comparison research, dated primary-source disclosures, RSS, and required article-specific social images. - A disclosed, code-backed JoodCMS integration case study with a generated 1600×900 WebP cover and 1200×630 JPEG Open Graph asset; no unsupported performance outcome is claimed. - Opt-in HMAC-signed image URLs, cloud object caching, ClickHouse analytics, daily cloud database backups, and graceful deployment health handling. - Cross-platform SEO drift checks for canonical, robots, schema, Open Graph, headings, snippets, and status regressions. - Hourly Postgres analytics rollups with migration backfill and transactional updates when new request logs are written. - Rollup-based analytics summaries, charts, top images, format/status/domain filters, and dashboard KPI deltas so normal dashboard queries no longer scan raw request logs. - The overview dashboard now includes recent request activity and instance operations health alongside the analytics cards. - Analytics and dashboard cards now reconcile Cloudflare edge delivery, Keenpix disk-cache delivery, and live origin processing into one source-split funnel. - Analytics charts gained edge/origin lenses for funnel, comparison, and Cloudflare-only views. ### Changed - Relicensed the current self-host engine to AGPL-3.0-only; previously published v0.1.11 and older releases retain their original Apache-2.0 terms. - Reworked the public experience for narrow mobile layouts, comfortable touch targets, better font discovery, factual metadata/schema, truthful sitemap dates, and canonical apex routing. - Comparison and pricing copy now distinguishes sourced vendor facts, estimates, product limitations, customer-set caps, and self-host operational responsibility. - Polar usage events use deterministic idempotency keys, plan changes use the billing portal, and portal sessions resolve from the organization billing customer. - Postmark failures now log provider status/error codes without leaking recipient details. - The dashboard and analytics pages now distinguish Cloudflare edge metrics from Keenpix origin-shield metrics so totals and trend badges do not mix incompatible sources. - Coolify deployments default back to the public `ghcr.io/lord007tn/keenpix:latest` image unless `KEENPIX_IMAGE` is set. ### Fixed - Corrected image analytics so failed responses no longer count as cache hits or optimized deliveries, cache-hit rates use successful deliveries consistently, and an exact rollup migration repairs existing counters. - Made Postgres request-log and rollup writes atomic with transient retries, and ensured a batch queued during a long flush cannot remain stranded. - Removed the duplicate first-party Web Vitals endpoint and browser package; Cloudflare RUM is now the single field-performance collector. - Corrected misleading Vercel 402, Cloudinary quota, ImageKit custom-domain, migration-URL, and unsupported universal image-savings claims. - Added missing Polar trial/uncancel webhook documentation and prevented duplicate checkout-based plan switches. - Fixed public header overflow at 320–412px through compact navigation and intrinsic-width containment. ## [v0.1.7] - 2026-06-13 ### Added - Optional Cloudflare edge-cache analytics using the Cloudflare GraphQL Analytics API. - A Settings -> CDN cache panel for storing an encrypted Cloudflare API token, Zone ID, and optional image hostname. - Linked-entity JSON-LD, shared error pages, shared 404 handling, and documentation Open Graph image routes. ### Changed - Analytics can now show Cloudflare edge hit-rate, edge requests, bytes served from the edge, and hourly hit/miss split for `/img/*`. - Edge analytics use a fixed last-24-hours window to match the Cloudflare adaptive dataset limit on non-enterprise plans. ## [v0.1.6] - 2026-06-12 ### Added - Project Settings gained richer pipeline, allowed-host, and operations configuration surfaces. - Analytics gained source-domain capture, filtering, and per-project source-domain breakdowns. - Operations now shows real cache hit-rate and reasserts persisted cache caps when loading health data. - The user menu now surfaces the app version. ### Changed - Replaced the previous app sidebar with the current top-tab command-center navigation. - Disk cache eviction now uses an index and directory sharding for faster large-cache maintenance. - Runtime cache caps can be managed from the app instead of only through environment variables. ## [v0.1.5] - 2026-06-10 ### Added - IPX-style Sharp modifiers for geometry, color, effects, extra formats, and animation controls. - SVG output optimization through SVGO with explicit `fmt=svg` support. - SSE-backed live logs streamed from the server. - Operations health for disk cache, memory cache, and transform queue pressure. - Playwright smoke coverage and a fresh smoke dev-server flow. - Expanded reference documentation for endpoint parameters, responses, SDK API, operations, and deployment presets. ### Changed - Improved image delivery p95 with disk-cache and transform-path performance work. - SDK handlers and server modules were reorganized around clearer boundaries. - Docker builds now use an Alpine image and improved layer caching. - Release-note generation now uses changelogithub defaults and includes conventional commit categories. ### Fixed - SVGO runtime dependencies are kept external so optimized SVG handling works in production builds. - SVG auto-format behavior is documented correctly: `fmt=auto` rasterizes, while `fmt=svg` preserves SVG output. ## [v0.1.4] - 2026-06-05 ### Added - Project-scoped analytics and per-domain breakdowns. - GitHub release automation through changelogithub. ### Changed - Analytics aggregations were made faster for project-scoped views. - Date handling now uses inline `dayjs` calls and product code avoids trivial formatter/helper wrappers. ## [v0.1.3] - 2026-06-03 ### Added - Internal API keys are now created from a modal with a one-time key reveal instead of an inline form. - SMTP settings gained a **Test** dialog for sending a test email to a chosen recipient. - API key **activity** is recorded and shown under **Workspace → API Keys** (method, path, status, latency). - The dashboard sidebar version links to the GitHub releases page. ### Changed - Analytics format and status filters now list only the values present in the selected window, matching Live Logs — no empty filter menus. - Live Logs: **Clear** now also clears the path search, the path search has an inline clear button, and the request **Path** column is wider. - Workspace tabs (Staff, Email, API Keys) now update the URL so links and refreshes keep the active tab. - SMTP **Save** is enabled only when there are unsaved changes. - Staff roles display human-readable labels (for example, "Super admin"). - Security and documentation copy now scopes "no API key" to transform URLs; internal API keys exist for the management API. ### Fixed - The mobile **Staff** layout no longer overflows horizontally. ## [v0.1.2] - 2026-06-02 ### Added - Added Better Auth API key support for trusted internal integrations. - Added a super-admin **Workspace → API Keys** panel to create, copy, list, and disable internal API keys. - Added `/api/sdk/*` project management endpoints for listing, creating, reading, updating settings, and adding/removing allowed domains. ### Changed - Kept the public `/img/*` transform endpoint headerless and allowlist-gated; internal API keys are only for product-management API calls. ## [v0.1.1] - 2026-06-01 ### Changed - Reorganized settings into clearer surfaces. Global workspace settings (staff management and email/SMTP) moved out of project Settings into a dedicated **Workspace** area reached from the user menu (super admins only). - Project **Settings** is now scoped to a single project — General, Pipeline, and Security — in a tabbed layout. - Consolidated SMTP configuration and the delivery check into one card: edit the connection, then save or send a test email from the same place. - Trimmed the **Account** page to Profile and Appearance, removing the duplicated security and workspace cards. ## [v0.1.0] - 2026-06-01 Initial public release for self-hosting. ### Added - Self-hostable image optimization service with PostgreSQL-backed projects, request logs, analytics, and admin settings. - CDN-friendly image transform endpoint at `/img/` with width, quality, format, fit, and DPR parameters. - Cloudflare/self-hosting CDN documentation, including cache-rule guidance for Cloudflare and non-Cloudflare proxies. - Docker image publishing through GHCR for `latest`, `v0.1`, and `v0.1.0` tags. - Project configuration UI for allowed hosts, default quality, default format, and cache TTL. - Admin SMTP and staff invitation management. ### Changed - Replaced the early `/api/keenpix?url=...` optimizer endpoint with the cleaner `/img/` path format. - Hardened form validation with shared schemas and clearer validation errors across project, auth, invite, and admin flows. - Improved release automation so GitHub releases are created from this changelog. ### Verified - CI passes lint, typecheck, tests, and production build. - Docker workflow builds and publishes the self-host image. --- Markdown path: https://keenpix.com/compare.md # Compare image CDN options > Dated, source-backed image CDN comparisons with explicit competitor wins and limitations. Canonical HTML: [https://keenpix.com/compare](https://keenpix.com/compare) Last reviewed: 2026-09-02 Keenpix writes these pages and benefits if you choose it. Each comparison uses primary vendor sources, visible assumptions, and a next-review date. - [Keenpix vs Cloudinary](https://keenpix.com/compare/cloudinary-alternative): Keenpix vs Cloudinary (August 2026): pooled credits vs one managed-delivery meter. Honest pricing, migration steps, and when each wins. - [Keenpix vs Cloudflare Images](https://keenpix.com/compare/cloudflare-images-alternative): Keenpix vs Cloudflare Images: managed-delivery pricing versus unique-transform pricing, storage choices, limits, migration steps, and when each wins. - [Keenpix vs Bunny Optimizer](https://keenpix.com/compare/bunny-optimizer-alternative): Keenpix vs Bunny Optimizer: flat optimizer fee plus CDN bandwidth versus managed-delivery plans, feature differences, migration steps, and when each wins. - [Keenpix vs imgix](https://keenpix.com/compare/imgix-alternative): Compare Keenpix and imgix after imgix's 2025 credit switch: one delivery meter, published rates, an AGPL self-host option, and a URL migration map. - [Keenpix vs imgproxy](https://keenpix.com/compare/imgproxy-alternative): Keenpix vs imgproxy (August 2026): managed image delivery vs a self-hosted transform engine. Compare costs, operations, security, migration, and fit. - [Keenpix vs ImageKit](https://keenpix.com/compare/imagekit-alternative): Keenpix vs ImageKit, August 2026: same $9 entry but 100 GB vs 40 GB, phase-specific overage, AGPL self-hosting — and where ImageKit still wins. - [Keenpix vs Gumlet Image](https://keenpix.com/compare/gumlet-alternative): Keenpix vs Gumlet Image (August 2026): compare bandwidth pricing, analytics, origin support, security, migration work, and self-hosting. - [Keenpix vs TwicPics](https://keenpix.com/compare/twicpics-alternative): Keenpix vs TwicPics (August 2026): compare bandwidth pricing, responsive-media tooling, video, migration work, operations, and self-hosting. - [Keenpix vs Vercel Image Optimization](https://keenpix.com/compare/vercel-image-optimization-alternative): Keenpix vs Vercel Image Optimization: one meter vs three, unlimited transforms, next/image loader support, and always-on paid usage. Pricing as of August 2026. Read the [comparison methodology](https://keenpix.com/methodology/comparisons). --- Markdown path: https://keenpix.com/developers.md # Keenpix developer resources > API discovery, project-scoped authentication, official SDK, and agent-readable public contracts. Canonical HTML: [https://keenpix.com/developers](https://keenpix.com/developers) Last reviewed: 2026-09-02 ## Discover the API - [OpenAPI 3.1 specification](https://keenpix.com/openapi.json) - [SDK API documentation](https://keenpix.com/docs/reference/sdk-api) - [Public JSON health endpoint](https://keenpix.com/api/health) ## Use the official SDK The supported automation client is the server-side @keenpix/sdk package. Keenpix does not publish an official CLI, OAuth authorization server, or separate API sandbox. ## Authentication and onboarding Control-plane operations use a project-scoped API key in Authorization: Bearer or X-Keenpix-Api-Key. Normal image delivery URLs are keyless after project and origin configuration. Keep keys in trusted server environments. ## Agent-readable sources - [Concise LLM index](https://keenpix.com/llms.txt) - [Complete Markdown collection](https://keenpix.com/llms-full.txt) - [Learning hub](https://keenpix.com/learn) Contact hi@raedbahri.com. --- Markdown path: https://keenpix.com/image-cdn-cost-calculator.md # Image CDN cost calculator > A source-dated workload model for comparing image delivery providers without treating estimates as quotes. Canonical HTML: [https://keenpix.com/image-cdn-cost-calculator](https://keenpix.com/image-cdn-cost-calculator) Last reviewed: 2026-09-02 ## Inputs Delivered GB, requests, unique transforms, source storage, projects, custom domains, and region. ## Method The calculator applies only public, source-backed pricing components implemented for each provider. Rows are labeled Estimated, Partial only, or Quote required. It does not invent enterprise discounts, CDN rates, taxes, or missing product behavior. ## Verify before buying Confirm the provider's current pricing page, define the same workload, include external CDN/storage/operations, and inspect the calculation coverage note. An estimate is not an invoice or savings guarantee. Use the interactive [canonical calculator](https://keenpix.com/image-cdn-cost-calculator) and read [transparent image CDN pricing](https://keenpix.com/blog/transparent-image-cdn-pricing). --- Markdown path: https://keenpix.com/learn.md # Keenpix Learn > Human-first learning paths for image CDN fundamentals, performance, cost, origins, security, operations, migrations, frameworks, and agent integration. Canonical HTML: [https://keenpix.com/learn](https://keenpix.com/learn) Last reviewed: 2026-09-03 Start with the problem, not the product. Every guide is designed to remain useful without its Keenpix next step. ## Image CDN fundamentals Understand the delivery path, responsive images, formats, and cache behavior before choosing a product. - [AVIF vs WebP in production: choose with your cache](https://keenpix.com/blog/avif-vs-webp-production-caching): Compare AVIF and WebP without borrowed benchmarks, then configure format negotiation and CDN cache variants without serving the wrong bytes. - [What is an image CDN? How optimization and delivery work](https://keenpix.com/blog/what-is-an-image-cdn): An image CDN fetches, transforms, caches, and delivers image variants close to users. Learn the request lifecycle, tradeoffs, and when you need one. - [Responsive image CDN guide: srcset, sizes, DPR, and formats](https://keenpix.com/blog/responsive-image-cdn-guide): Build responsive image CDN URLs with srcset and sizes, choose width candidates, handle DPR and format negotiation, and avoid common cache mistakes. - [Image CDN vs traditional CDN: caching and transforms](https://keenpix.com/blog/image-cdn-vs-traditional-cdn): A traditional CDN caches existing files. An image CDN creates optimized variants and then caches them. Compare responsibilities, costs, and use cases. ## Performance Measure real image delivery, control variant growth, and improve the slow path without invented benchmarks. - [How to measure image CDN performance reproducibly](https://keenpix.com/blog/reproducible-image-performance-measurement): Build a repeatable image delivery test that separates cold transforms, warm caches, transfer, decoding, rendering, and field experience. - [Stop cache stampedes in image transformation](https://keenpix.com/blog/image-transform-cache-stampedes-capacity): Coalesce cold image transforms, serve stale safely, shed overload, and size CPU and memory capacity from measured service time and working sets. - [Image CDN cache keys: tame Vary: Accept](https://keenpix.com/blog/image-cdn-cache-keys-vary-accept): Design image cache keys that keep AVIF, WebP, and JPEG correct without multiplying variants for every raw Accept header or query permutation. ## Cost and buying Model the whole bill, compare service boundaries, and decide when managed or self-hosted delivery fits. - [Best Image CDNs in 2026: 10 Services Compared](https://keenpix.com/blog/best-image-cdns-2026): Compare 10 image CDN and optimization services using verified August 2026 pricing, use cases, tradeoffs, and clear best-fit recommendations for developers. - [Cloudinary Credits Explained: Pricing vs Keenpix](https://keenpix.com/blog/keenpix-vs-cloudinary): Learn how Cloudinary credits combine transformations, storage, and bandwidth, then compare that meter with Keenpix managed-delivery pricing. - [imgix Credits Explained: Delivery and Storage Pricing](https://keenpix.com/blog/keenpix-vs-imgix): Learn how imgix credit packages combine managed media and delivery allowances, then compare the meter with Keenpix managed-delivery pricing. - [ImageKit Bandwidth Pricing Explained: Limits and Overage](https://keenpix.com/blog/keenpix-vs-imagekit): Learn how ImageKit bandwidth allowances and overage work, then compare their boundary with Keenpix managed-delivery pricing. - [Image CDN Pricing: Why Keenpix Bills by Managed Delivery](https://keenpix.com/blog/transparent-image-cdn-pricing): See why Keenpix bills for optimized managed delivery instead of transformations, with published plans, included transforms, and always-on metered overage. - [Self-hosted vs managed image optimization: how to choose](https://keenpix.com/blog/self-host-vs-managed-image-optimization): Compare who owns the database, cache, delivery bill, upgrades, monitoring, and failure recovery in self-hosted and managed image optimization. ## Origins and architecture Keep ownership of source assets while designing a safe, cacheable path from origin to delivery edge. - [Design a user-upload image pipeline end to end](https://keenpix.com/blog/user-upload-image-pipeline-design): Design image uploads from authenticated ingestion and byte validation through quarantine, moderation, durable storage, transformation, delivery, and recovery. - [Bring-your-own-origin image CDN architecture](https://keenpix.com/blog/bring-your-own-origin-image-cdn-architecture): Design an image delivery layer around source assets you already own, with explicit origin, cache, URL, security, and recovery boundaries. ## Security Separate public delivery from private origins, constrain fetches, and protect transform URLs from abuse. - [Private image origin security boundaries](https://keenpix.com/blog/private-image-origins-security-boundaries): Choose between public assets, signed source grants, authenticated gateways, and private networks without leaking origin credentials into delivery URLs. - [Secure image pipelines: SSRF to image bombs](https://keenpix.com/blog/secure-image-pipelines-ssrf-image-bombs): Defend image fetch and transform services with origin allowlists, redirect revalidation, byte and pixel ceilings, timeouts, and observable failures. - [Signed image URLs with HMAC: stop unauthorized transforms](https://keenpix.com/blog/signed-image-urls-hmac): Learn when an origin allowlist is enough, when HMAC signing helps, how Keenpix canonicalizes a transform request, and what secret rotation breaks. ## Operations and troubleshooting Diagnose symptoms from the browser back to the origin, then deploy, observe, and roll back safely. - [Cache invalidation with versioned image URLs](https://keenpix.com/blog/cache-invalidation-versioned-image-urls): Use immutable versioned image URLs across browser, CDN, and origin caches, with safe publication, rollback, purge, revocation, and verification patterns. - [Safe image CDN rollouts and rollbacks](https://keenpix.com/blog/safe-image-cdn-rollouts-and-rollbacks): Canary image delivery changes with explicit acceptance gates, cache-aware rollback, observability, and a tested path back to the previous URL generator. - [Image delivery troubleshooting: diagnose by symptom](https://keenpix.com/blog/image-delivery-troubleshooting-by-symptom): Trace broken, slow, stale, oversized, or incorrectly cropped images from browser selection through caches, transforms, and origin access. - [Self-host an image CDN with Docker: production checklist](https://keenpix.com/blog/self-host-image-cdn-docker): Deploy Keenpix with a pinned Docker image, persistent Postgres, health checks, CDN cache rules, backups, and a repeatable smoke test. ## Migrations Translate URLs and behavior, canary representative traffic, and retain a tested reversal path. - [Vercel Image Optimization to Keenpix Migration](https://keenpix.com/blog/migrate-vercel-image-optimization-to-keenpix): Move next/image from Vercel's optimizer to a Keenpix custom loader with config mapping, canary rollout, cache and origin checks, and rollback. - [ImageKit to Keenpix Migration Guide](https://keenpix.com/blog/migrate-imagekit-to-keenpix): Move ImageKit transforms and origins to Keenpix with a compatibility table, asset-export gate, canary plan, cache checks, and rollback. - [Cloudinary to Keenpix Migration Guide](https://keenpix.com/blog/migrate-cloudinary-to-keenpix): Migrate Cloudinary image URLs to Keenpix with an explicit parameter map, origin plan, canary rollout, cache checks, and tested rollback. ## Frameworks Connect responsive image markup and framework loaders without hiding the underlying delivery contract. - [How to optimize Next.js images with a custom CDN loader](https://keenpix.com/blog/nextjs-custom-image-cdn-loader): Connect next/image to Keenpix with a custom loader, responsive sizes, format negotiation, and an origin allowlist you can verify in the browser. - [How JoodCMS integrates Keenpix image delivery](https://keenpix.com/blog/joodcms-keenpix-integration): A code-backed case study of JoodCMS using Keenpix for responsive variants, source allowlists, domain sync, prewarming, and safe fallbacks. ## Agent integration Give coding agents bounded, inspectable context and verify their integration against the same public contracts. - [Agent-assisted image CDN integration without blind trust](https://keenpix.com/blog/agent-assisted-image-cdn-integration): Give a coding agent public, bounded contracts for image delivery, then verify generated URLs, security boundaries, responsive behavior, and rollback. --- Markdown path: https://keenpix.com/legal/dpa.md # Data Processing Addendum Last updated: July 13, 2026 This Data Processing Addendum ("DPA") forms part of the [Terms of Service](/legal/terms) between you ("Controller") and Keenpix ("Processor") and applies where Keenpix processes personal data on your behalf in providing the service. Where terms conflict, this DPA controls for data-protection matters. ## 1. Roles and scope You are the controller of the personal data you route through Keenpix (including data embedded in or associated with the images and requests of your end users). Keenpix is the processor and processes such data only on your documented instructions, which include your configuration of the service and these terms. ## 2. Nature and purpose of processing Keenpix fetches, transforms, caches, and delivers images from origins you control, and records per-request operational metadata to power your analytics, logs, and managed-delivery billing. The categories of data subjects and personal data are those you choose to route through the service. ## 3. Confidentiality Keenpix ensures that personnel authorized to process personal data are bound by confidentiality obligations. ## 4. Security Keenpix implements appropriate technical and organizational measures, including encryption in transit, tenant isolation, hashed credentials, and least-privilege access, to protect personal data against unauthorized access, loss, or disclosure. ## 5. Sub-processors You authorize Keenpix to engage the sub-processors listed in our [Privacy Policy](/legal/privacy) (hosting/database, Cloudflare R2, ClickHouse, Polar, Postmark). Keenpix imposes data-protection obligations on each sub-processor no less protective than this DPA and remains responsible for their performance. We will give reasonable notice of new sub-processors so you may object on reasonable grounds. ## 6. International transfers Where personal data is transferred across borders, Keenpix relies on lawful transfer mechanisms (such as Standard Contractual Clauses) with its sub-processors as applicable. ## 7. Data subject requests and assistance Taking into account the nature of the processing, Keenpix will provide reasonable assistance to help you respond to data-subject requests and to meet your obligations regarding security, breach notification, and impact assessments. ## 8. Breach notification Keenpix will notify you without undue delay after becoming aware of a personal-data breach affecting your data, with information reasonably available to help you meet your notification obligations. ## 9. Deletion and return On termination, Keenpix will delete or return personal data processed on your behalf within a reasonable period, except where retention is required by law. You may also delete projects and data from the app at any time. ## 10. Audits Keenpix will make available information reasonably necessary to demonstrate compliance with this DPA and allow for audits on reasonable prior notice, subject to confidentiality. ## Contact To raise a data-protection matter or request a signed copy of this DPA, email [hi@raedbahri.com](mailto:hi@raedbahri.com). Canonical HTML: [https://keenpix.com/legal/dpa](https://keenpix.com/legal/dpa) --- Markdown path: https://keenpix.com/legal/license.md # License & Open Source Last updated: August 5, 2026 Keenpix is open source. The same engine that powers the managed cloud is published in our public repository and can be self-hosted with no Keenpix license fee. You remain responsible for infrastructure, operations, and any third-party charges. There is no separate "open-core" fork — the cloud is this engine, operated for you. ## Engine license The Keenpix engine is licensed under the [GNU Affero General Public License v3.0 (AGPL-3.0)](https://github.com/lord007tn/keenpix/blob/master/LICENSE). You are free to run it on your own infrastructure subject to that license, including for commercial use. If you modify the engine and offer it to others over a network, the AGPL requires you to share your modifications' source. See the [self-hosting guide](/docs/self-hosting) to deploy it with Docker. Our promise: the self-host engine stays AGPL-licensed and free — no rug-pull, no contributor license agreement, no features removed from self-host to upsell the cloud. Releases published before the AGPL relicense (v0.1.11 and earlier) remain available under Apache-2.0. ## Cloud service Your use of the hosted Keenpix cloud service is governed by our [Terms of Service](/legal/terms) and [Privacy Policy](/legal/privacy), not by the engine's open-source license. The open-source license covers the software; the cloud terms cover the operated service. ## Third-party software Keenpix builds on excellent open-source projects, including sharp for image processing, TanStack Start and React for the application, Prisma and PostgreSQL for data, ClickHouse for analytics, and better-auth for authentication. Their respective licenses apply to those components. ## No lock-in Because the engine is open source and cloud and self-host share one URL scheme, you can move between managed cloud and self-hosting without rewriting your image URLs. ## Editorial content and machine-readable licensing The source-code license does not by itself define a separate machine-readable license for Keenpix editorial content. Keenpix does not publish an RSL declaration or another editorial-content license without an explicit legal decision and approved text. Canonical HTML: [https://keenpix.com/legal/license](https://keenpix.com/legal/license) --- Markdown path: https://keenpix.com/legal/privacy.md # Privacy Policy Last updated: September 5, 2026 This Privacy Policy explains how Keenpix ("we", "us") collects, uses, and protects personal data when you use the Keenpix cloud service. Where you use Keenpix to process data on behalf of your own users, you are the controller and we act as your processor under the [Data Processing Addendum](/legal/dpa). ## Data we collect - **Account data:** name, email address, and authentication credentials (passwords are stored only as salted hashes). - **Workspace and project configuration:** project names, allowlisted origin hosts, and pipeline settings you create. - **Operational logs and analytics:** per-request metadata — timestamp, requested path, image format, response status, cache result, latency, and bytes delivered — used to power your dashboard and to bill managed delivery. Coarse country may be recorded when a trusted edge supplies it; it is otherwise left empty. We do not store the image bytes beyond the transform cache. - **Billing data:** handled by our merchant of record, Polar. We receive subscription status and customer identifiers; we do not store full card details. - **Optional website analytics:** after you explicitly consent, Google Analytics receives page and funnel events such as CTA clicks, signup, project creation, and checkout. Google Analytics may also collect the page location and title, referrer, browser and device information, approximate location, analytics cookies, and interactions enabled through Enhanced Measurement. Keenpix does not intentionally send account, organization, project, image, API key, or email data. Advertising storage and personalization remain disabled. - **Website performance telemetry:** Cloudflare Web Analytics measures page views, load timing, and Core Web Vitals such as LCP, INP, and CLS for visitors in all configured regions, including the EU. Its beacon uses no cookies or browser storage and Cloudflare states that source IP addresses are discarded at its nearest data center. ## How we use data - to operate, secure, and improve the service; - to render your analytics and logs and to meter managed delivery for billing; - to send transactional email (verification, password reset, invitations, billing notices) via our email provider, Postmark; and - to comply with legal obligations and enforce our Terms. We do not sell personal data or use service data for targeted advertising. ## Sub-processors We rely on a small set of infrastructure sub-processors: our hosting and database providers, Cloudflare for traffic delivery, security, R2 image caching, and Web Analytics, ClickHouse for analytics storage, Polar for payments, and Postmark for email. Each processes data only to provide its function. ## Analytics choices Google Analytics is off until you choose “Allow analytics.” Declining does not affect the service. You can change your choice at any time using Optional analytics on this page or Account preferences. Your choice is remembered in this browser. Keenpix also respects the browser’s Do Not Track setting by not loading Google analytics. Declining stops future Google funnel reports, updates Google Consent Mode to denied, and removes Keenpix-domain Google Analytics cookies. Cloudflare’s separate cookie-free Web Analytics beacon remains active for site performance measurement in all regions, including the EU, and is not used for Keenpix account or image-project analytics. ## Retention Operational logs are retained according to your plan's retention window; aggregated analytics are retained for up to one year. Account data is kept while your account is active and deleted (or anonymized) within a reasonable period after account closure, subject to legal retention requirements. ## Your rights Depending on your location, you may have rights to access, correct, export, or delete your personal data, and to object to or restrict certain processing. To exercise these rights, email [hi@raedbahri.com](mailto:hi@raedbahri.com). You can also delete projects and close your account from the app at any time. ## Children Keenpix is not directed to children under 16, and we do not knowingly collect their personal data. If you believe a child has provided data, contact us so we can review and delete it where appropriate. ## Security We encrypt data in transit, isolate workspaces by tenant, hash passwords, and follow least-privilege access. No system is perfectly secure, but we work to protect your data and to disclose material incidents promptly. ## Contact Questions about privacy? Email [hi@raedbahri.com](mailto:hi@raedbahri.com). Canonical HTML: [https://keenpix.com/legal/privacy](https://keenpix.com/legal/privacy) --- Markdown path: https://keenpix.com/legal/terms.md # Terms of Service Last updated: August 5, 2026 These Terms of Service ("Terms") govern your access to and use of the Keenpix cloud service operated by Keenpix ("Keenpix", "we", "us"). By creating an account or using the service you agree to these Terms. If you are using Keenpix on behalf of an organization, you represent that you are authorized to bind that organization. ## 1. The service Keenpix is a hosted image-optimization proxy and CDN. You point Keenpix at images you are authorized to use, and we transform and deliver them. Keenpix does not host your source images; it fetches them from origins you configure and control. The open-source engine is separately available for self-hosting under its own license (see the [License](/legal/license)). ## 2. Accounts and workspaces You are responsible for your account credentials and for all activity in your workspace. You must provide accurate information and keep your email address current. You must be at least 16 years old to use Keenpix. ## 3. Acceptable use You agree not to use Keenpix to: - proxy or deliver content you do not own or have permission to use, or that infringes intellectual-property rights; - deliver unlawful, infringing, or abusive content, including CSAM, which results in immediate termination and reporting; - attempt to breach isolation between workspaces, probe the service for vulnerabilities without authorization, or circumvent usage limits; or - distribute malware, phishing material, spam, or other content intended to deceive or harm others; or - use the service in a way that overloads, disrupts, or impairs it for other customers. ## 4. Plans, billing, and usage Paid plans are billed through our merchant of record, Polar, which handles payment processing, invoicing, and applicable taxes. Each plan includes a monthly managed-delivery allotment. Successful optimized responses delivered through the Keenpix-managed edge or application count once; delivery above that allotment is billed at the published per-GB overage rate for your plan. Whether Cloudflare, Keenpix's optimized-variant cache, or a new origin transform supplies that response does not change the billable count. Saved bandwidth is separate analytics and is never added to delivered bytes. Transforms, requests, and team members are not separately metered. Subscriptions renew automatically until cancelled; you can cancel at any time from your billing portal and retain access through the end of the paid period. Fees are non-refundable except where required by law. If you believe a charge is incorrect, contact us with the invoice or receipt before opening a payment dispute so we can investigate it. ## 5. Your content and origins You retain all rights to your images. You grant Keenpix a limited license to fetch, transform, cache, and deliver them solely to provide the service. You are responsible for the origins you allowlist and for the legality of the content served through your projects. ## 6. Availability and changes We aim for high availability but do not guarantee uninterrupted service. We may modify or discontinue features with reasonable notice. Material changes to these Terms will be posted here with an updated date. ## 7. Termination You may stop using Keenpix at any time. We may suspend or terminate access for violation of these Terms, non-payment, a credible security risk, or activity that may harm the service or others. Where practical, we will give notice and an opportunity to fix the issue. Because the engine is open source, you can migrate to a self-hosted deployment and keep your transform URLs. ## 8. Disclaimers and liability The service is provided "as is" without warranties of any kind. To the maximum extent permitted by law, Keenpix's aggregate liability for any claim arising from the service is limited to the amounts you paid in the three months preceding the claim. We are not liable for indirect or consequential damages. ## 9. Ownership and feedback Keenpix and its licensors retain rights in the service, software, documentation, and branding. These Terms do not transfer ownership of your content or Keenpix intellectual property. If you send product feedback, you allow us to use it without restriction or payment. ## 10. Your responsibility You are responsible for claims arising from content, origins, or instructions you provide, including claims that they infringe another party's rights. Keep independent copies of source assets and do not use the transform cache as your only backup. ## 11. Contact Questions about these Terms? Email [hi@raedbahri.com](mailto:hi@raedbahri.com). Canonical HTML: [https://keenpix.com/legal/terms](https://keenpix.com/legal/terms) --- Markdown path: https://keenpix.com/methodology/comparisons.md # How Keenpix comparisons are researched > How Keenpix verifies competitor facts, calculates pricing scenarios, discloses conflicts, and corrects comparison pages. Canonical HTML: [https://keenpix.com/methodology/comparisons](https://keenpix.com/methodology/comparisons) Last reviewed: 2026-09-02 ## Source order 1. Official pricing, product documentation, limits, and legal terms. 2. Official release notes and support statements. 3. Reproducible product behavior where public documentation is incomplete. Undocumented behavior stays labeled unknown. No customer ratings, market-share claims, or performance benchmarks are published without inspectable evidence. ## Pricing comparisons Every scenario states its workload and uses the public price checked on the page date. Taxes, contracts, legacy plans, regional differences, and external CDN charges remain limitations. ## Product scope and licensing A media platform, image optimizer, CDN feature, and self-hosted transform engine are not interchangeable. Source-code licenses are checked from the applicable release. ## Review and corrections Commercial facts are reviewed at least quarterly and after material announcements. Send the page, disputed statement, primary source, and check date to hi@raedbahri.com. ## What a verdict means “Best for” is a fit judgment for stated criteria, not a universal ranking. Every comparison states when the competitor is the better choice. --- Markdown path: https://keenpix.com/pricing.md # Keenpix image CDN pricing > Managed image delivery is billed once by optimized bytes delivered, with published plan allowances and overage rates. Self-hosting has no Keenpix license fee but transfers infrastructure and operations to you. Canonical HTML: [https://keenpix.com/pricing](https://keenpix.com/pricing) Last reviewed: 2026-09-02 Managed plans include a 14-day trial. A card is required, trial usage is never billed, and paid delivery continues through overage instead of stopping at the plan allowance. | Plan | Monthly price | Included delivery | Overage | Projects | Custom domains | | --- | --- | --- | --- | --- | --- | | Basic | $9/month | 100 GB | $0.12/GB | 5 | 0 | | Pro | $29/month | 400 GB | $0.09/GB | 25 | 1 | | Business | $69/month | 1000 GB | $0.07/GB | Unlimited | 10 | ## What every managed plan includes ### Basic - 100 GB managed delivery / month - Unlimited transforms — AVIF, WebP + 6 more formats - Unlimited team members - 5 projects - Bandwidth-saved, cache-hit & top-image analytics - Live request logs · 30-day retention - Signed URLs + per-project allowlists - Always-on overage · $0.12/GB ### Pro - 400 GB managed delivery / month - Advanced analytics — geo, latency percentiles, 365-day history - Full log search · 90-day retention - 25 projects - 1 custom delivery domain - Cheaper overage · $0.09/GB, billed monthly ### Business - 1 TB managed delivery / month - Unlimited projects - 10 custom delivery domains - Add 5 more custom domains for $5/month - 365-day log retention - Lowest overage · $0.07/GB, billed monthly ## Self-hosting The released engine can be self-hosted under AGPL-3.0 with no Keenpix license fee. You pay for and operate compute, storage, databases, backups, monitoring, upgrades, security, and delivery infrastructure. ## Billing boundary and limitations Managed billing counts successful optimized bytes delivered through the Keenpix-managed application and edge once. Pricing shown here is the current public self-service catalog returned by the same pricing source as the HTML page. Taxes, merchant-of-record behavior, legacy subscriptions, and future plan changes can affect a real invoice. Confirm the checkout total before purchase. ## Next steps Use the [cost-model guide](https://keenpix.com/blog/transparent-image-cdn-pricing), the [workload calculator](https://keenpix.com/image-cdn-cost-calculator), or [compare managed and self-hosted delivery](https://keenpix.com/blog/self-host-vs-managed-image-optimization). Keenpix is not the right fit if you need a DAM, video platform, or a capability excluded by the product documentation. --- Markdown path: https://keenpix.com/security.md # Security at Keenpix > Keenpix is an image transformation service, so its first security boundary is deciding which origins and URLs it may fetch. This page documents the controls present in the product; it is not a certification or penetration-test claim. Canonical HTML: [https://keenpix.com/security](https://keenpix.com/security) Last reviewed: 2026-09-02 ## Origin and request controls Projects allowlist origin hostnames. Outbound fetches are SSRF-hardened, and optional HMAC-signed URLs can restrict who may create transform requests. No public project API secret needs to be embedded in image markup. ## Data handled by the cloud service The managed service processes source image URLs, transformed image bytes, cache entries, and request telemetry needed for delivery and analytics. Keenpix is not a digital asset manager and does not ask you to move master assets into a Keenpix media library. ## Deployment choice Teams that require infrastructure control can run the AGPL-3.0 engine themselves. Self-hosting transfers responsibility for network policy, updates, backups, monitoring, and incident response to the operator. ## Claims we do not make Keenpix does not currently claim SOC 2, ISO 27001, PCI certification, a public bug-bounty program, or a contractual uptime SLA. Those claims will appear only if supporting evidence exists. ## Report a vulnerability Email hi@raedbahri.com with a clear description, affected URL or component, and reproduction steps. Do not include live credentials or customer data, and do not access data that is not yours. We ask for reasonable time to investigate before public disclosure. ## Related public sources - [Keenpix documentation](https://keenpix.com/docs) - [Product changelog](https://keenpix.com/changelog) - [Support and corrections](https://keenpix.com/support) --- Markdown path: https://keenpix.com/self-hosted-image-cdn.md # The self-hosted image CDN that keeps the dashboard > Run the Keenpix image transformation, cache, dashboard, analytics, origin controls, and signed delivery stack on infrastructure you operate. Canonical HTML: [https://keenpix.com/self-hosted-image-cdn](https://keenpix.com/self-hosted-image-cdn) Last reviewed: 2026-09-02 ## What is included The released AGPL-3.0 stack includes transforms, cache tiers, per-project origin allowlists, signed URLs, dashboard operations, and delivery analytics. There is no Keenpix license fee. ## Your responsibilities You own compute, storage, database, CDN configuration, TLS, network policy, backups, upgrades, monitoring, capacity, and incident response. Self-hosting is not a universal cost shortcut. ## Architecture Place a correctly configured CDN in front of the transform service and include every byte-changing input in the cache identity. Verify query strings and Accept variation before traffic. ## Limitations and alternatives Keenpix is not a DAM or video platform. Choose imgproxy when you want a focused transform primitive, or a full media platform when uploads, library workflows, and video belong together. ## Procedure Follow the [version-synchronized self-hosting guide](https://keenpix.com/docs/self-hosting), [health and operations](https://keenpix.com/docs/self-hosting/health-and-operations), and [safe rollout guide](https://keenpix.com/blog/safe-image-cdn-rollouts-and-rollbacks). --- Markdown path: https://keenpix.com/status.md # Service status > Keenpix does not yet publish a historical uptime percentage or a third-party status dashboard. The live health endpoint reports current application health; it is a point-in-time signal, not an SLA or availability history. Canonical HTML: [https://keenpix.com/status](https://keenpix.com/status) Last reviewed: 2026-09-02 ## Current health Use the public /api/health endpoint for the cloud application’s current health response. A successful check does not prove that every region, origin, customer project, or upstream service is healthy. ## Suspected incident If delivery or the dashboard is failing, contact hi@raedbahri.com with the affected project, request URL with secrets removed, response status, approximate time, and region. Do not send API tokens, signing secrets, or payment details. ## Incident communication Material cloud incidents will be documented through the public changelog or release notes when confirmed. A dedicated incident-history service remains planned; no historical uptime figure is inferred before it exists. ## Related public sources - [Keenpix documentation](https://keenpix.com/docs) - [Product changelog](https://keenpix.com/changelog) - [Support and corrections](https://keenpix.com/support) --- Markdown path: https://keenpix.com/support.md # Keenpix support > Support is available by email for managed-cloud account, billing, delivery, and security questions. Public documentation and GitHub are the best starting points for product behavior and self-hosting issues. Canonical HTML: [https://keenpix.com/support](https://keenpix.com/support) Last reviewed: 2026-09-02 ## Managed cloud Email hi@raedbahri.com with your account email, project identifier, relevant timestamps, and a redacted example URL. No response-time SLA is currently promised, so do not design an incident plan around an unstated guarantee. ## Open-source and self-hosting Use the public repository for reproducible software defects and feature requests. Keep credentials, private origins, customer data, and production logs out of public issues. ## Editorial corrections For comparison or article corrections, include the page URL, disputed sentence, a primary source, and the date you checked it. Substantive corrections receive a new visible updated date. ## Related public sources - [Keenpix documentation](https://keenpix.com/docs) - [Product changelog](https://keenpix.com/changelog) - [Support and corrections](https://keenpix.com/support) --- Markdown path: https://keenpix.com/compare/cloudinary-alternative.md # Cloudinary Alternative: Keenpix vs Cloudinary (2026) > Keenpix vs Cloudinary (August 2026): pooled credits vs one managed-delivery meter. Honest pricing, migration steps, and when each wins. Canonical HTML: [https://keenpix.com/compare/cloudinary-alternative](https://keenpix.com/compare/cloudinary-alternative) Author: Raed Bahri, Keenpix founder and maintainer Published: 2026-08-31 Last reviewed: 2026-08-31 If you need video transcoding, a full digital asset manager, or AI-powered transforms today, pick Cloudinary — its breadth is real and Keenpix does not offer those capabilities. If what you use Cloudinary for is resizing and delivering images, Keenpix offers one managed-delivery meter, unlimited transformations and team members, managed custom domains, always-on paid usage, and an AGPL-3.0 v0.3.1 self-host path. Be aware of what you give up: Keenpix is a young solo-founder product with no video or storage. As of August 31, 2026, the Keenpix standard catalog lists 400 GB/month at $29; Cloudinary bandwidth draws from the Advanced plan’s pooled credits alongside transforms and storage. Facts verified: 2026-08-31. Pricing checked: August 31, 2026. Next review: 2026-10-12. ## Decision summary Cloudinary bundles transformations, storage, and bandwidth into credits. Keenpix bills exactly one thing: optimized bytes delivered through managed cloud, with unlimited transforms and team members plus always-on paid overage. ## Feature comparison | Capability | Cloudinary | Keenpix | | --- | --- | --- | | Billing model | Pooled credits (transforms + storage + GB) | One meter: optimized managed delivery | | Transformations | Metered — 1 credit per 1,000 | Unlimited on every plan | | Entry paid plan | $99/mo · 225 pooled credits ($89 annual) | $9/mo · 100 GB managed delivery (both pricing phases) | | Overage handling | Soft limits; plan upgrade or custom terms may be required | Linear $0.07–0.12/GB | | Paid overage behavior | Soft limits; plan changes or custom terms may apply | Always on at the published per-GB rate | | Self-hosting | No | v0.3.1: AGPL-3.0 with Docker deployment files | | Analytics | Usage reports, plan-dependent | Every tier: cache hits, latency, live logs | | API security | Public API key + secret model | No public keys — origin allowlists + HMAC URLs | | Storage / DAM | Yes — full DAM included | No — serves your existing origins | | Video | Yes — transcoding + streaming | No | | AI features | Yes — tagging, gen fill, smart crop | Not available | | Custom domain (CNAME) | Advanced plan ($249/mo) and up | Pro: 1 · Business: 10 | | CDN | Bundled (Cloudinary's edge) | Sits behind your CDN (Cloudflare etc.) | | Modern formats | AVIF/WebP via f_auto | AVIF/WebP negotiation built in | ## Pricing scenarios These scenarios use the assumptions shown on the canonical page. They are estimates, not quotes; taxes, contracts, regions, and vendor changes can alter the result. | Scenario | Cloudinary | Keenpix | | --- | --- | --- | | Credits → GB translation | 1 credit = 1 GB bandwidth OR 1k transforms OR 1 GB storage, one pool | No translation — 1 GB managed delivery is 1 GB metered | | 100 GB delivered / month | $99/mo Plus — 100 of 225 credits gone before any transforms | $9/mo Basic | | 400 GB delivered / month | $249/mo Advanced — Plus's 225 credits can't cover it | $29 Pro | | 1 TB delivered / month | Custom/enterprise — past Advanced's 600 credits | $69 Business | | Effective cost per GB | ~$0.44 per credit-GB on Plus | Plan effective rate varies; overage $0.07–0.12 | | The month you exceed the plan | Cloudinary documents soft limits and contacts customers about upgrading | Linear overage; paid delivery continues | ## When Cloudinary is the better fit - You need video transcoding, adaptive streaming, or video AI — Keenpix is images only. - You want a full DAM: upload pipelines, asset workflows, approvals, team libraries. Keenpix has no storage at all. - You rely on AI transforms like auto-tagging, background removal, or generative fill. - You need Cloudinary’s DAM, video, upload, and AI-media tooling in one platform. - You want a large, battle-tested vendor with a huge SDK ecosystem and enterprise support; Keenpix is a young, solo-founder product. ## Reasons teams may switch ### One meter you can actually forecast Cloudinary credits cover transformations, storage, and bandwidth at different rates, so forecasting needs all three dimensions. Keenpix meters optimized managed delivery once across Cloudflare edge hits and application responses. ### Published linear overage instead of a pooled estimate Cloudinary documents its plan limits as soft limits and says it contacts customers about upgrading. Keenpix bills linear per-GB overage while paid delivery continues, with usage alerts and projected charges visible during the billing period. ### Unlimited transformations make optimizing free Every Keenpix plan includes unlimited transforms, so adding AVIF variants, tighter responsive breakpoints, or art-directed crops costs nothing extra. On Cloudinary each of those experiments consumes credits from the same pool that pays for your bandwidth. Optimize aggressively without watching a meter. ### An open-source escape hatch The published v0.3.1 release is AGPL-3.0 and includes Docker/Coolify deployment files. Moving in-house still requires infrastructure, operations, and a hostname/routing plan. Cloudinary has no self-host path. ### Core analytics on every Keenpix tier Bandwidth saved, cache hit rate, format mix, top images, latency percentiles, and live request logs are included on every Keenpix tier. You should not need a sales call to find out what your images are doing. ## Migration path 1. Leave your assets where they are. Keenpix has no storage, so originals stay in S3, R2, your own server — or even in Cloudinary storage, used purely as an origin. You are only replacing the transform-and-delivery layer. 2. Create a Keenpix project and allowlist your origin hosts (e.g. your-bucket.s3.amazonaws.com or res.cloudinary.com). Per-project allowlists replace public API keys entirely. 3. Translate URLs: https://res.cloudinary.com//image/upload/w_800,q_75/photo.jpg becomes https://cdn.keenpix.com/p//img/?w=800&q=75. Cloudinary's f_auto needs no parameter — AVIF/WebP negotiation is automatic. c_fill,w_800,h_600 maps to w=800&h=600&fit=cover. 4. Swap URL generation in code with a small helper or your framework's image loader. Common resize, crop, quality, and format parameters have documented Keenpix equivalents; test every modifier your application uses before switching. 5. Use the managed Keenpix delivery hostname or custom domain so edge and application delivery are attributed once. You can place a customer-owned CDN in front when you want to operate an additional cache layer. 6. Optionally enable HMAC signed URLs on hotlink-sensitive routes. 7. Run both side by side through the 14-day trial, watch cache hit rate, projected usage cost, and bandwidth-saved analytics, then plan a canary cutover with the required URL or hostname routing changes. ## Verification checklist - Export one normal and one peak 30-day window of delivered image GB, managed storage GB, first-time derived transforms, and any separately billed add-ons. Keep video and DAM workload separate when Keenpix would not replace it. - Run the same allowlisted source images through both services for resize, crop, quality, and format negotiation. Record output dimensions, content type, response headers, visual fixtures, and every Cloudinary operation with no Keenpix equivalent. - Request every test URL cold and warm, then compare cache behavior, failure responses, signing, invalidation, observability, and the exact pooled credits or managed-delivery units recorded by each dashboard. - Canary one bounded route for a complete traffic cycle. Accept only if output, errors, latency, projected cost, and rollback meet thresholds written before the test. ## Frequently asked questions ### How do Cloudinary credits translate to gigabytes? One credit buys 1 GB of bandwidth, 1,000 transformations, or 1 GB of managed storage — all drawn from a single pool. Plus ($99/mo) includes 225 credits, so 225 GB delivered is the theoretical maximum if you spend zero credits on transforms and storage; real delivery is always less. Keenpix instead publishes one plan price plus one delivered-GB overage rate per plan. ### What happens when I hit my plan limit? Cloudinary describes its plan limits as soft limits and says it contacts customers about upgrading to a plan that better fits their usage. Keenpix bills $0.07–0.12/GB, keeps paid delivery online, and charges accumulated overage at the end of the billing period. Confirm either service's current terms before relying on a failure mode. ### Do I have to migrate my images out of Cloudinary? No. Keenpix has no storage — it fetches from origins you allowlist and handles transformation and delivery. You can keep originals in S3, R2, your own server, or even in Cloudinary storage during the transition, and move them later (or never). ### Does Keenpix handle video or DAM workflows? No. Keenpix is deliberately an image optimization and delivery layer. If video transcoding or a full digital asset manager is core to your workflow, Cloudinary is the better fit today. ### Is the self-hosted version feature-limited? The published v0.3.1 release provides the engine under AGPL-3.0 and includes the transform pipeline, caching, and analytics. Self-hosting still requires your own infrastructure and operations. ### Is there a free tier? The managed cloud has a 14-day free trial (card required), with plans starting at $9/month for 100 GB of optimized managed delivery. A released open-source version can be self-hosted without a Keenpix license fee, but you pay for and operate the infrastructure. ## Sources - [Cloudinary pricing](https://cloudinary.com/pricing), checked 2026-08-31. - [Cloudinary plan comparison and limits](https://cloudinary.com/pricing/compare-plans), checked 2026-08-31. - [Cloudinary credit documentation](https://cloudinary.com/documentation/developer_onboarding_faq_credits), checked 2026-08-31. - [Cloudinary plan-limit FAQ](https://cloudinary.com/faq), checked 2026-08-31. - [Keenpix pricing](/pricing), checked 2026-08-31. - [Keenpix transform parameters](/docs/reference/parameters), checked 2026-08-31. - [Keenpix v0.3.1 release](https://github.com/lord007tn/keenpix/releases/tag/v0.3.1), checked 2026-08-31. ## Limitations and disclosure Keenpix publishes this comparison and benefits if you choose it. “Best” means fit for the stated criteria, not a universal ranking. Vendor facts and prices can change after 2026-08-31; verify the linked primary sources before purchasing. No unmeasured performance, customer-rating, market-share, or savings claim is implied. ## Using Keenpix [Review Keenpix pricing](https://keenpix.com/pricing), [read the migration guides](https://keenpix.com/learn#topic-migrations), or [use the self-hosting path](https://keenpix.com/docs/self-hosting). Choose Cloudinary when the competitor-win criteria above match your requirements. --- Markdown path: https://keenpix.com/compare/cloudflare-images-alternative.md # Cloudflare Images Alternative: Keenpix vs Cloudflare (2026) > Keenpix vs Cloudflare Images: managed-delivery pricing versus unique-transform pricing, storage choices, limits, migration steps, and when each wins. Canonical HTML: [https://keenpix.com/compare/cloudflare-images-alternative](https://keenpix.com/compare/cloudflare-images-alternative) Author: Raed Bahri, Keenpix founder and maintainer Published: 2026-08-22 Last reviewed: 2026-08-22 Choose Cloudflare Images when you want Cloudflare-native storage, custom-domain delivery, Workers integration, or a free allowance for up to 5,000 unique remote-image transformations each month. Choose Keenpix when your originals already live elsewhere and managed delivery is the clearest planning unit. These meters are not interchangeable: Cloudflare can be less expensive for repeated delivery of a small variant set, while Keenpix can be easier to forecast when responsive variants change frequently. Test your real catalog and traffic before moving. Facts verified: 2026-08-22. Pricing checked: August 2026. Next review: 2026-10-12. ## Decision summary Cloudflare Images is deeply integrated with the Cloudflare edge and meters unique transformations or hosted-image storage and delivery. Keenpix meters optimized bytes delivered through its managed edge and application, counted once, with no transformation or seat charge. ## Feature comparison | Capability | Cloudflare Images | Keenpix | | --- | --- | --- | | Primary meter | Unique transforms; hosted storage and deliveries | Optimized managed delivery | | Free managed tier | 5,000 unique remote transforms/month | 14-day cloud trial; no free managed tier | | Original storage | Optional Cloudflare Images storage | Use your existing origin | | Custom delivery domain | Yes | Pro: 1 · Business: 10 | | Workers integration | Native Images binding and fetch workflows | HTTP URL API | | Automatic formats | AVIF, WebP, JPEG selection with fallbacks | AVIF/WebP negotiation | | Origin controls | Zone origin rules or Workers logic | Per-project origin allowlists and optional signed URLs | | Self-hosting | Managed Cloudflare service | v0.3.0 code is AGPL-3.0; you operate it | | Image analytics | Cloudflare Images usage metrics | Delivery, cache, format, latency, and request analytics | ## Pricing scenarios These scenarios use the assumptions shown on the canonical page. They are estimates, not quotes; taxes, contracts, regions, and vendor changes can alter the result. | Scenario | Cloudflare Images | Keenpix | | --- | --- | --- | | Up to 5,000 unique remote transformations / month | $0 on Images Free; cached existing variants continue after the limit | $9/mo Basic; transformations are not separately metered | | 10,000 unique remote transformations / month | $2.50 on Images Paid after the first 5,000 included | No transformation charge; managed delivery determines the plan | | 100,000 images stored and 100,000 hosted deliveries / month | $5 storage + $1 delivery on Images Paid | No managed storage; $9 Basic includes 100 GB managed delivery | | Remote-origin image delivery | $0.50 per 1,000 unique transformations after 5,000 included | $0.07–$0.12/GB overage after the plan allowance | ## When Cloudflare Images is the better fit - Your workload stays within 5,000 unique remote transformations per month and the free allowance is sufficient. - You want Cloudflare to store originals, issue direct-upload URLs, and deliver hosted images from custom domains. - Your application already uses Workers and benefits from the native Images binding and edge controls. - You repeatedly deliver a stable, small set of variants; unique-transform pricing may be cheaper than managed-delivery pricing. - You need a large global vendor, enterprise support options, and one Cloudflare platform for networking and media. ## Reasons teams may switch ### Forecast from delivered traffic Keenpix bills delivered gigabytes and does not add a charge for each width, crop, or output format. This can simplify forecasting for applications whose variant set changes frequently. ### Keep the origin and CDN separable Keenpix reads from allowlisted origins and is designed to sit behind an edge cache. That separation can suit teams that do not want image optimization tied to one storage or CDN account. ### A self-operated path The current v0.3.0 code is AGPL-3.0 and includes deployment files. Self-hosting removes a Keenpix service bill but adds infrastructure and operational work. ## Migration path 1. Inventory whether each source is a remote image or stored in Cloudflare Images; export hosted originals to an origin you control before changing delivery. 2. Create a Keenpix organization project and allowlist every source hostname. 3. Map Cloudflare width, height, fit, quality, and format options to documented Keenpix query parameters; test unsupported or advanced options separately. 4. Run representative URLs side by side and compare visual output, cache headers, file size, and latency. 5. Place your chosen CDN in front of Keenpix, verify its query-string and Accept-header cache behavior, and test cache hits. 6. Canary a small traffic share, monitor Keenpix managed delivery and errors, then migrate only after the results meet your acceptance thresholds. ## Frequently asked questions ### Is Keenpix always cheaper than Cloudflare Images? No. Cloudflare gives 5,000 unique remote transformations per month free and charges by unique transformation after that; repeat requests for the same transformation in a calendar month do not add transformation usage. Keenpix charges for optimized managed delivery across its edge and application. The lower-cost model depends on variant count, cache behavior, and bytes delivered. ### Can Keenpix replace Cloudflare Images storage? No. Keenpix does not store originals or provide upload workflows. Keep images in R2, S3, or another origin you operate, and use Keenpix only for transformation and delivery. ### What happens on Cloudflare Images Free after 5,000 transformations? Cloudflare documents that existing cached transformations continue to serve, while new transformations return error 9422. The free plan does not charge overage. Confirm current limits before production rollout. ## Sources - [Cloudflare Images pricing](https://developers.cloudflare.com/images/pricing/), checked 2026-08-22. - [Cloudflare remote transformation overview](https://developers.cloudflare.com/images/optimization/transformations/overview/), checked 2026-08-22. - [Cloudflare Images limits and formats](https://developers.cloudflare.com/images/get-started/limits/), checked 2026-08-22. - [Keenpix pricing](/pricing), checked 2026-08-22. ## Limitations and disclosure Keenpix publishes this comparison and benefits if you choose it. “Best” means fit for the stated criteria, not a universal ranking. Vendor facts and prices can change after 2026-08-22; verify the linked primary sources before purchasing. No unmeasured performance, customer-rating, market-share, or savings claim is implied. ## Using Keenpix [Review Keenpix pricing](https://keenpix.com/pricing), [read the migration guides](https://keenpix.com/learn#topic-migrations), or [use the self-hosting path](https://keenpix.com/docs/self-hosting). Choose Cloudflare Images when the competitor-win criteria above match your requirements. --- Markdown path: https://keenpix.com/compare/bunny-optimizer-alternative.md # Bunny Optimizer Alternative: Keenpix vs Bunny.net (2026) > Keenpix vs Bunny Optimizer: flat optimizer fee plus CDN bandwidth versus managed-delivery plans, feature differences, migration steps, and when each wins. Canonical HTML: [https://keenpix.com/compare/bunny-optimizer-alternative](https://keenpix.com/compare/bunny-optimizer-alternative) Author: Raed Bahri, Keenpix founder and maintainer Published: 2026-08-22 Last reviewed: 2026-08-22 Bunny Optimizer is a strong choice when you want a mature CDN, custom hostnames, per-website flat optimizer pricing, and broader website optimization including CSS and JavaScript minification. Keenpix is a focused alternative when you want image-only delivery, a single bundled managed-delivery plan, detailed image analytics, always-on metered overage, or a self-operated path. Bunny can be cheaper at high volume depending on CDN region and tier; Keenpix may be simpler when a bundled allowance is more useful than a separate optimizer fee and CDN-bandwidth bill. Facts verified: 2026-08-22. Pricing checked: August 2026. Next review: 2026-10-12. ## Decision summary Bunny Optimizer charges $9.50 per website for unlimited optimizations, requests, and transformations, with Bunny CDN bandwidth billed separately. Keenpix bundles a managed-delivery allowance and unlimited transforms into each plan. ## Feature comparison | Capability | Bunny Optimizer | Keenpix | | --- | --- | --- | | Billing model | Optimizer fee per website + CDN bandwidth | Bundled managed-delivery plan | | Transformations | Unlimited | Unlimited | | CDN | Bunny CDN pull zone | Use the CDN you choose | | CSS / JavaScript minification | Yes | No — image pipeline only | | Dynamic image API | Resize, crop, quality, format, effects, and watermarking | Core resize, crop, quality, format, and image modifiers | | Custom delivery hostname | Yes through Bunny CDN | Pro: 1 · Business: 10 | | Storage | Optional Bunny Storage, billed separately | No storage; use your existing origin | | Spend control | Account billing and CDN controls | Usage alerts; paid delivery stays online | | Self-hosting | Managed Bunny service | v0.3.0 code is AGPL-3.0; you operate it | ## Pricing scenarios These scenarios use the assumptions shown on the canonical page. They are estimates, not quotes; taxes, contracts, regions, and vendor changes can alter the result. | Scenario | Bunny Optimizer | Keenpix | | --- | --- | --- | | Optimizer service | $9.50 per website/month; unlimited optimization, requests, and transformations | Included in each $9–$69 managed-delivery plan; unlimited transformations | | 100 GB image delivery / month | $9.50 + Bunny CDN bandwidth at the applicable regional/tier rate | $9 Basic, 100 GB included | | 400 GB image delivery / month | $9.50 + Bunny CDN bandwidth at the applicable regional/tier rate | $29 Pro, 400 GB included | | 1 TB image delivery / month | $9.50 + Bunny CDN bandwidth at the applicable regional/tier rate | $69 Business, 1 TB included | | Additional website / pull zone | Another $9.50 optimizer fee; contact sales above 20 websites | Projects share the organization managed-delivery allowance; confirm plan limits | ## When Bunny Optimizer is the better fit - You want Bunny CDN and Optimizer configured together with custom delivery hostnames. - You need automatic CSS and JavaScript minification in addition to image optimization. - Your traffic is high enough that Bunny CDN regional or volume rates produce a lower tested total cost. - You use Bunny Storage, Stream, Shield, or its broader platform and prefer one established vendor. - You need enterprise support options, a larger operating team, or a mature global CDN footprint. ## Reasons teams may switch ### One bundled image-delivery allowance Keenpix combines optimization and managed delivery into one allowance, counted once across its edge and application. This can be easier to budget than a per-pull-zone optimizer fee plus region-dependent CDN bandwidth, though it is not automatically cheaper. ### Image-specific operational analytics Keenpix exposes cache hit rate, bytes saved, format mix, latency percentiles, top images, and live request logs on every tier so teams can inspect the image pipeline directly. ### A self-operated deployment option The current v0.3.0 code is AGPL-3.0 and includes Docker and Coolify deployment paths. Self-hosting requires operating and securing the service yourself. ## Migration path 1. Export your current Bunny pull-zone and Optimizer settings, including allowed origins, cache rules, tokens, image classes, and every transform parameter in use. 2. Create a Keenpix organization project and allowlist the original source hosts; Bunny Storage can remain an origin during a staged migration. 3. Map Bunny width, height, crop, quality, format, and other used parameters to documented Keenpix equivalents. Treat CSS/JavaScript minification and unsupported effects as separate workloads. 4. Generate representative URLs from production traffic and compare output dimensions, visual quality, formats, cache headers, and bytes. 5. Put a CDN in front of Keenpix, configure query-string and Accept-header cache variation correctly, then verify cache hits before sending user traffic. 6. Canary traffic, compare the complete Bunny optimizer-plus-bandwidth cost with Keenpix managed delivery, and cut over only after error and quality checks pass. ## Frequently asked questions ### Is Bunny Optimizer cheaper than Keenpix? It can be. Bunny charges $9.50 per website for Optimizer and bills CDN bandwidth separately; Keenpix includes managed delivery in plans starting at $9. The answer depends on Bunny region and tier, the number of websites or pull zones, bytes delivered, and any existing Bunny commitments. ### Does Bunny Optimizer meter transformations? Bunny documents unlimited optimization, requests, and transformations in its $9.50 per-website fee. CDN bandwidth is billed separately. ### Does Keenpix replace every Bunny Optimizer feature? No. Keenpix is image-focused and does not replace Bunny CSS or JavaScript minification, its CDN platform, storage, custom hostnames, or every advanced image effect. Inventory and test the exact features you use. ## Sources - [Bunny Optimizer pricing documentation](https://docs.bunny.net/optimizer/pricing), checked 2026-08-22. - [Bunny Optimizer product pricing](https://bunny.net/pricing/optimizer/), checked 2026-08-22. - [Bunny Optimizer product overview](https://bunny.net/optimizer/), checked 2026-08-22. - [Bunny Optimizer dynamic image documentation](https://docs.bunny.net/optimizer/dynamic-images/quality), checked 2026-08-22. - [Keenpix pricing](/pricing), checked 2026-08-22. ## Limitations and disclosure Keenpix publishes this comparison and benefits if you choose it. “Best” means fit for the stated criteria, not a universal ranking. Vendor facts and prices can change after 2026-08-22; verify the linked primary sources before purchasing. No unmeasured performance, customer-rating, market-share, or savings claim is implied. ## Using Keenpix [Review Keenpix pricing](https://keenpix.com/pricing), [read the migration guides](https://keenpix.com/learn#topic-migrations), or [use the self-hosting path](https://keenpix.com/docs/self-hosting). Choose Bunny Optimizer when the competitor-win criteria above match your requirements. --- Markdown path: https://keenpix.com/compare/imgix-alternative.md # imgix Alternative: Keenpix vs imgix (2026) > Compare Keenpix and imgix after imgix's 2025 credit switch: one delivery meter, published rates, an AGPL self-host option, and a URL migration map. Canonical HTML: [https://keenpix.com/compare/imgix-alternative](https://keenpix.com/compare/imgix-alternative) Author: Raed Bahri, Keenpix founder and maintainer Published: 2026-08-31 Last reviewed: 2026-08-31 imgix has a broader rendering API — if you lean on face-aware cropping, text overlays, video, or its long tail of advanced parameters, Keenpix won't replace that today, and a mature vendor with enterprise support may matter more to you than price. imgix's credit model draws delivery and cached-storage management from one pool, with published self-serve packages from $25 Starter through $500 Growth Plus. Keenpix charges for managed delivery, publishes self-serve overage rates, and includes analytics on every tier. For core resize, crop, quality, and modern-format delivery, compare both services on your own traffic during the trial; do not assume feature or output parity. Facts verified: 2026-08-31. Pricing checked: August 31, 2026. Next review: 2026-10-12. ## Decision summary Keenpix bills optimized bytes delivered through managed cloud — no credits, management meter, request fees, or seat charges. Published v0.3.1 is AGPL-3.0 and can be self-hosted without a Keenpix license fee, or used as a managed service from $9/month. ## Feature comparison | Capability | imgix | Keenpix | | --- | --- | --- | | Pricing model | Credits: 1/GB delivered + 2/GB/mo managed storage | Delivered GB only — one meter | | Published pricing | Self-serve packages to $500/mo Growth Plus | Every tier and overage rate published | | Entry plan | $25/mo · 100 credits | $9/mo · 100 GB managed delivery | | Transformations | Feature-dependent credit use; verify the operation mix | Unlimited, never metered | | Rendering API depth | Best-in-class, very deep | Documented core resize, crop, quality, and format modifiers | | Auto AVIF/WebP | Yes (auto=format) | Yes — Accept-header negotiation | | Analytics | Usage reporting | Every tier: cache hits, bandwidth saved, latency, live logs | | Self-hosting | No | v0.3.1: AGPL-3.0 with Docker deployment files | | Signed URLs | Yes (HMAC) | Optional HMAC; no public API keys | | Works behind your CDN | Bundled CDN (Fastly) | Designed to sit behind Cloudflare etc. | | Video | Yes | No | | Custom domains | Yes | Pro: 1 · Business: 10 | | Overage behavior | Confirm current credit-limit handling with imgix | Usage alerts and projected charges; no normal hard stop | | Vendor profile | Established since 2011, premium brand | Young, solo-founder, open source | ## Pricing scenarios These scenarios use the assumptions shown on the canonical page. They are estimates, not quotes; taxes, contracts, regions, and vendor changes can alter the result. | Scenario | imgix | Keenpix | | --- | --- | --- | | 100 GB delivered / month | $25/mo Starter (100 credits, before management credits) | $9/mo Basic — 100 GB included | | 400 GB delivered / month | $150/mo Midrange (830 credits, before management credits) | $29 Pro — 400 GB included | | 1 TB delivered / month | $300/mo Growth (1,875 credits) | $69 Business — 1 TB included | | 50 GB of cached originals under management | 100 credits/mo (2 credits per GB/mo) | $0 — no storage or management meter | | Overage beyond plan | Published packages end at $500/mo Growth Plus; larger needs require current terms | $0.07–0.12/GB; paid delivery stays online | | Transformations | Credit use varies by feature; delivery and management also draw credits | Unlimited on every plan — managed delivery is the only meter | ## When imgix is the better fit - You depend on imgix's deep rendering features — face-aware cropping, text overlays, palette extraction, and the long tail of parameters Keenpix doesn't replicate. - You need video processing and delivery from the same pipeline; Keenpix is images only. - You need the broader mature imgix ecosystem rather than Keenpix’s focused transform-and-deliver workflow. - You want an established vendor (operating since 2011) with enterprise support and SLAs rather than a young, solo-founder product. - Your usage fits comfortably inside a current imgix plan and renewal terms you've confirmed — switching has real costs, and 'it works and the price is known' is a fine reason to stay. ## Reasons teams may switch ### One meter you can actually predict imgix's August 2025 credit model bills delivery at 1 credit/GB and managed cached storage at 2 credits per GB per month, from one pooled balance. Keenpix has one billing meter: optimized bytes delivered through its managed edge and application, counted once. ### Published pricing, no renewal roulette Keenpix publishes overage of $0.07–0.12/GB for every self-serve tier. imgix publishes packages through Growth Plus at $500/month; larger requirements need current terms. Compare a written imgix quote with your measured workload rather than relying on third-party anecdotes. ### Always-on paid usage with transparent rates imgix publishes credit allowances, but teams should confirm current overage and enforcement behavior directly for their plan. Keenpix continues paid delivery at its published per-GB rate and shows projected charges throughout the billing period. ### An AGPL self-host option in v0.3.1 The published v0.3.1 release is AGPL-3.0 and includes Docker/Coolify deployment files. You can operate it in-house while owning infrastructure and routing. imgix is managed-only. ### Analytics on every plan, not a premium add-on Bandwidth saved, cache hit rate, format mix, top images, latency percentiles, and live request logs come with the $9/mo plan. You can see exactly what the optimizer is doing for you — which is also how you verify whether switching was worth it. ## Migration path 1. Create a Keenpix project and allowlist your origin host(s). Keenpix pulls from the same S3 bucket or web folder imgix reads from, so no assets move and there are no API keys to manage. 2. Map your URL parameters. Most carry over unchanged: w→w, h→h, q→q, fit→fit, dpr→dpr, blur→blur. Renames: fm→fmt, rot→rotate, rect/crop→extract. Mirroring: imgix flip=v becomes flip, flip=h becomes flop. 3. Rewrite the URL shape: https://yoursub.imgix.net/hero.jpg?w=800&fm=avif becomes https://cdn.keenpix.com/p//img/https://origin.example.com/hero.jpg?w=800&fmt=avif. You can usually drop fm entirely — Keenpix negotiates AVIF/WebP automatically from the Accept header. 4. Update your image URL helper or framework loader. Most codebases build imgix URLs in one function, so this is typically a single-file change. 5. Put Keenpix behind your existing CDN (Cloudflare or similar — it's designed for that) and run both services side by side on a slice of traffic, diffing output with the live request logs. 6. Watch projected overage, cache hit rate, and managed delivery in the dashboard for a week. 7. Cut the remaining traffic over and cancel imgix before the next credit-cycle renewal. ## Verification checklist - Export one normal and one peak month of imgix delivery GB, managed-media GB, and every transformation class that consumes credits; do not substitute package maximums for measured use. - Run the same allowlisted source images through both services for resize, crop, quality, DPR, format negotiation, and each advanced imgix operation you rely on. Record output dimensions, content type, response headers, and visual fixtures. - Request every test URL cold and warm, then compare cache behavior, failure responses, signing, invalidation, observability, and the exact credit or managed-delivery units recorded by each dashboard. - Canary one bounded route for a complete traffic cycle. Accept only if output, errors, latency, projected cost, and rollback meet thresholds written before the test. ## Frequently asked questions ### Is Keenpix a drop-in replacement for imgix? For resize, crop, quality, DPR, and format workloads — which covers most imgix usage — yes, with a mechanical parameter rename (fm→fmt, rot→rotate, rect→extract; the rest map 1:1). It is not a replacement for imgix's video features or its most advanced rendering parameters. ### What changed with imgix pricing in August 2025? imgix moved its published plans to a credit model. Current monthly packages are Starter $25 for 100 credits, Basic $75 for 375, Midrange $150 for 830, Growth $300 for 1,875, and Growth Plus $500 for 3,570. Delivery costs 1 credit per GB and management costs 2 credits per GB per month of cached storage, from one pooled balance. ### How should I compare imgix enterprise pricing? Request a current written quote from imgix and model it against your own delivery and managed-source usage. Keenpix does not use unverified third-party renewal anecdotes as pricing evidence. ### How does Keenpix billing work? One meter: managed image delivery. Basic is $9 for 100 GB then $0.12/GB, Pro is $29 for 400 GB then $0.09/GB, and Business is $69 for 1 TB then $0.07/GB. Cloudflare edge hits and application responses count once. Transformations and team members are unlimited; paid usage continues and accumulated overage is charged at period end. ### Can I self-host Keenpix instead of paying for cloud? The published v0.3.1 release is licensed AGPL-3.0 and includes Docker and Coolify deployment files. Self-hosting has no Keenpix license fee, but you operate the infrastructure. ### What does Keenpix not do? No video, storage, or DAM: Keenpix transforms and delivers from origins you already have. Managed custom domains are included on Pro and Business. It is also a young product from a solo founder, so buyers should weigh vendor maturity and support needs. ## Sources - [imgix pricing](https://www.imgix.com/pricing), checked 2026-08-31. - [imgix credit pricing FAQ](https://www.imgix.com/pricing-faq), checked 2026-08-31. - [imgix credit consumption guide](https://www.imgix.com/credit-consumption), checked 2026-08-31. - [imgix rendering API](https://docs.imgix.com/en-US/apis/rendering/overview), checked 2026-08-31. - [imgix payment policy](https://www.imgix.com/payments), checked 2026-08-31. - [Keenpix pricing](/pricing), checked 2026-08-31. - [Keenpix transform parameters](/docs/reference/parameters), checked 2026-08-31. - [Keenpix v0.3.1 release](https://github.com/lord007tn/keenpix/releases/tag/v0.3.1), checked 2026-08-31. ## Limitations and disclosure Keenpix publishes this comparison and benefits if you choose it. “Best” means fit for the stated criteria, not a universal ranking. Vendor facts and prices can change after 2026-08-31; verify the linked primary sources before purchasing. No unmeasured performance, customer-rating, market-share, or savings claim is implied. ## Using Keenpix [Review Keenpix pricing](https://keenpix.com/pricing), [read the migration guides](https://keenpix.com/learn#topic-migrations), or [use the self-hosting path](https://keenpix.com/docs/self-hosting). Choose imgix when the competitor-win criteria above match your requirements. --- Markdown path: https://keenpix.com/compare/imgproxy-alternative.md # imgproxy Alternative: Keenpix vs imgproxy (2026) > Keenpix vs imgproxy (August 2026): managed image delivery vs a self-hosted transform engine. Compare costs, operations, security, migration, and fit. Canonical HTML: [https://keenpix.com/compare/imgproxy-alternative](https://keenpix.com/compare/imgproxy-alternative) Author: Raed Bahri, Keenpix founder and maintainer Published: 2026-08-22 Last reviewed: 2026-08-22 Choose imgproxy when your team wants direct control of the transform service, already operates containers and a CDN, values its Apache-2.0 core license, or needs an imgproxy-specific processing feature. Choose Keenpix managed cloud when you want a vendor to operate transformation, delivery, caches, usage metering, analytics, abuse controls, and updates behind one service. imgproxy open source has no license fee, but that is not a zero-cost delivery system: compute, egress, CDN, storage, observability, on-call work, upgrades, and capacity remain yours. imgproxy Pro starts at $49/month or $499/year for up to 16 workers and adds advanced features and support; infrastructure remains separate. Keenpix starts at $9/month for 100 GB of managed delivery with published overage. Keenpix is a younger, solo-founder product and imgproxy has a mature standalone engine with a broad processing surface, so teams that already own the platform work may rationally prefer imgproxy. Facts verified: 2026-08-22. Pricing checked: August 2026. Next review: 2026-10-12. ## Decision summary imgproxy is a fast, mature image-processing server you operate. Keenpix combines an open-source path with a managed image CDN, a dashboard, usage analytics, and one delivered-GB cloud meter. The real choice is ownership, not a fake feature-score winner. ## Feature comparison | Capability | imgproxy | Keenpix | | --- | --- | --- | | Product boundary | Standalone image-processing server | Managed image transformation and delivery service plus a self-host release | | Open-source license | Apache-2.0 core | AGPL-3.0 published release | | Deployment | Docker is recommended; an official Helm chart is available | Managed cloud or documented Docker/Coolify self-host deployment | | URL protection | HMAC URL signatures with key and salt; recommended for production | Origin allowlists and optional HMAC signed URLs | | Modern formats | AVIF, WebP, JPEG XL, and other libvips-supported formats | Automatic AVIF/WebP negotiation plus explicit format controls | | Processing depth | Broad standalone processing surface; several advanced features are Pro-only | Focused resize, crop, quality, DPR, blur, format, and responsive delivery surface | | Processed-image cache | You provide the cache/CDN architecture; imgproxy Pro includes an internal cache | Managed memory, disk, object-storage, and delivery caching | | Dashboard and projects | No managed multi-project SaaS dashboard in the open-source server | Projects, origin settings, API keys, domains, team access, and billing UI | | Usage analytics | Prometheus/OpenTelemetry building blocks; you store and visualize telemetry | Bandwidth, cache, format, latency, top-image analytics, and request logs | | Operations | You own upgrades, scaling, security patches, alerts, backups, and incidents | Keenpix owns the managed service; self-host users retain those responsibilities | | Support | Community for OSS; priority creator support is a Pro benefit | Managed product support by email and WhatsApp; open-source issue tracker for public code | | Video previews and ML features | Available among imgproxy Pro capabilities | No video product; AI extensions are not claimed as generally available | ## Pricing scenarios These scenarios use the assumptions shown on the canonical page. They are estimates, not quotes; taxes, contracts, regions, and vendor changes can alter the result. | Scenario | imgproxy | Keenpix | | --- | --- | --- | | Open-source license | Apache-2.0 core with no imgproxy license fee | AGPL-3.0 self-host release with no Keenpix license fee | | Managed entry point | No managed CDN plan; you assemble and operate the delivery stack | $9/month for 100 GB managed delivery | | Commercial feature tier | Pro starts at $49/month or $499/year for up to 16 workers | Managed plans are $9/$29/$69 monthly; no separate transform-worker license | | Compute and scaling | Your servers, autoscaling, queues, concurrency, and capacity margin | Included in managed cloud; your workload is billed by delivered GB | | CDN delivery and egress | Separate provider bill and configuration | Included in the managed-delivery meter; customer-owned CDN hits do not reach Keenpix | | A 400 GB managed workload | Cannot be derived from license price alone; model infrastructure, CDN, egress, and labor | $29/month Pro before any overage | ## When imgproxy is the better fit - Your platform team already operates Kubernetes or container services, a CDN, metrics, logs, alerts, and incident response, so another managed control plane adds little value. - Apache-2.0 compatibility is a hard requirement and AGPL-3.0 is not acceptable for your self-hosted distribution model. - You need a specific imgproxy processing option, codec, video-preview feature, or Pro ML capability that Keenpix does not provide. - You want to tune worker concurrency, CPU and memory allocation, networking, cache topology, and release timing directly. - Your traffic is large and predictable enough that owned infrastructure plus engineering time is demonstrably cheaper in your environment. - You prefer imgproxy’s mature standalone engine and release history over adopting a younger managed product from a solo founder. ## Reasons teams may switch ### Replace a stack diagram with one managed boundary A production imgproxy deployment normally sits beside compute orchestration, a delivery CDN, DNS/TLS, metrics, logs, alerting, secrets, rate controls, and an upgrade process. Keenpix managed cloud contracts those responsibilities into a service and exposes project settings and usage in one dashboard. ### Forecast a delivery bill from delivered GB imgproxy’s zero-dollar open-source license and worker-priced Pro license do not include infrastructure or delivery. Keenpix managed plans publish included delivered GB and linear overage, so the calculator can estimate a managed bill without pretending engineering time is free. ### Give product teams self-service visibility Keenpix includes project analytics, request logs, cache behavior, format mix, bandwidth savings, domains, and usage projections. An imgproxy team can build an excellent observability stack, but it must choose, connect, secure, retain, and operate those components. ### Keep an escape hatch without operating it today Keenpix publishes a self-host path for teams that later need control, while the managed cloud removes the immediate operational load. The licenses differ: evaluate AGPL-3.0 obligations for Keenpix and Apache-2.0 for imgproxy with your own counsel. ### Separate origin trust by project Managed Keenpix projects use explicit origin allowlists, optional signatures, quotas, and isolated settings. imgproxy has strong source restrictions and signed URLs, but multi-tenant project governance remains part of the platform you design around the server. ## Migration path 1. Inventory every imgproxy URL option, preset, source scheme, signature rule, output format, and Pro-only feature in production. Mark any capability that has no Keenpix equivalent before changing traffic. 2. Export a representative URL corpus and expected dimensions, content types, cache headers, visual crops, and failure responses. Use it as a regression set rather than relying on a few hand-picked images. 3. Create one Keenpix project per trust and ownership boundary, then allowlist only the source hosts each project needs. Do not carry a permissive source policy into the managed configuration. 4. Translate supported resize, crop, quality, DPR, blur, and format operations into Keenpix query parameters. Automatic AVIF/WebP negotiation can replace explicit format selection where that matches your cache strategy. 5. Replace imgproxy path signatures with the Keenpix project URL shape and optional HMAC signature. Rotate keys and keep the old path available during the overlap instead of attempting a flag-day migration. 6. Canary a small, measurable traffic slice. Compare visual output, origin fetches, cache hit rate, latency, error classes, and projected delivery cost under real traffic and multiple device widths. 7. Move the remaining traffic only after the regression corpus and canary meet your thresholds. Keep a rollback route until cache behavior and billing projections remain stable through a complete traffic cycle. ## Frequently asked questions ### Is imgproxy free? The imgproxy open-source core is Apache-2.0 and has no imgproxy license fee. A production service still needs compute, network egress, a CDN or cache strategy, DNS/TLS, monitoring, logs, upgrades, security work, and on-call ownership. imgproxy Pro is commercial and starts at $49/month or $499/year for up to 16 workers as of August 22, 2026. ### Does Keenpix replace every imgproxy feature? No. Keenpix focuses on common web image transformation and managed delivery. imgproxy has a broad processing surface and Pro features including advanced compression, dynamic watermarks, video previews, and ML-assisted operations. Inventory real production URLs before considering a move. ### Which option is cheaper? There is no honest universal answer. Keenpix publishes a managed delivered-GB price. imgproxy open source publishes no service fee because you supply the service; Pro prices workers but not infrastructure or CDN delivery. Model vendor bills and the operational work your team will actually own. ### Can imgproxy sign transformation URLs? Yes. imgproxy supports HMAC URL signatures using a key and salt and recommends enabling signing in production. Keenpix also supports signed delivery URLs and adds per-project origin allowlists in its managed control plane. ### Can I self-host Keenpix instead? A published Keenpix release is available under AGPL-3.0 with container deployment files. Self-hosting Keenpix also means you operate its infrastructure. Review the license and deployment architecture rather than treating either open-source option as managed service. ### How should I compare a self-hosted engine with a managed CDN? Use a total-cost worksheet: license, compute headroom, CDN and egress, object storage, monitoring, log retention, security patching, upgrades, incident response, and engineering ownership. Then compare feature fit, data boundaries, recovery expectations, and the value of direct control. ## Sources - [imgproxy pricing](https://imgproxy.net/pricing/), checked 2026-08-22. - [imgproxy FAQ](https://imgproxy.net/faq/), checked 2026-08-22. - [imgproxy installation](https://docs.imgproxy.net/latest/installation), checked 2026-08-22. - [imgproxy URL signing](https://docs.imgproxy.net/latest/usage/signing_url), checked 2026-08-22. - [imgproxy source and license](https://github.com/imgproxy/imgproxy), checked 2026-08-22. - [imgproxy releases](https://github.com/imgproxy/imgproxy/releases), checked 2026-08-22. - [Keenpix pricing](/pricing), checked 2026-08-22. - [Keenpix self-host documentation](/docs/self-hosting), checked 2026-08-22. ## Limitations and disclosure Keenpix publishes this comparison and benefits if you choose it. “Best” means fit for the stated criteria, not a universal ranking. Vendor facts and prices can change after 2026-08-22; verify the linked primary sources before purchasing. No unmeasured performance, customer-rating, market-share, or savings claim is implied. ## Using Keenpix [Review Keenpix pricing](https://keenpix.com/pricing), [read the migration guides](https://keenpix.com/learn#topic-migrations), or [use the self-hosting path](https://keenpix.com/docs/self-hosting). Choose imgproxy when the competitor-win criteria above match your requirements. --- Markdown path: https://keenpix.com/compare/imagekit-alternative.md # ImageKit Alternative: Keenpix vs ImageKit (2026) > Keenpix vs ImageKit, August 2026: same $9 entry but 100 GB vs 40 GB, phase-specific overage, AGPL self-hosting — and where ImageKit still wins. Canonical HTML: [https://keenpix.com/compare/imagekit-alternative](https://keenpix.com/compare/imagekit-alternative) Author: Raed Bahri, Keenpix founder and maintainer Published: 2026-08-22 Last reviewed: 2026-08-22 If you want an all-in-one media platform — upload widgets, a media library your marketing team can browse, video streaming, and a free tier under 20 GB/month — ImageKit is the better product. Keenpix deliberately does none of that. Pick Keenpix if your images already live on origins you control and you need optimization and delivery with predictable costs: the same $9 entry buys 100 GB instead of 40 GB, overage is $0.07–0.12/GB, and paid delivery stays online. Published v0.3.0 also provides an AGPL-3.0 self-host path. Keenpix is younger and solo-built — weigh that honestly against ImageKit's maturity. Facts verified: 2026-08-22. Pricing checked: August 2026. Next review: 2026-10-12. ## Decision summary ImageKit publishes bandwidth pricing; Keenpix publishes a managed-delivery meter. Keenpix bundles more delivery at its entry tier and v0.3.0 is AGPL-3.0, while ImageKit offers a much broader media platform. ## Feature comparison | Capability | ImageKit | Keenpix | | --- | --- | --- | | Entry paid plan | $9/mo · 40 GB bandwidth | $9/mo · 100 GB managed delivery | | Overage rate | $0.45–0.50/GB | $0.07–0.12/GB | | Free tier | 20 GB/mo — serving stops when exceeded | None — 14-day trial (card required) | | Billing meters | Bandwidth + storage | Delivered bandwidth only — one meter | | Transformations | Unlimited | Unlimited on every plan | | Team members | 3 on Lite; paid additions on Pro | Unlimited on every paid plan | | When you hit the limit | Free plan: hard-stop mid-month | Paid plans keep serving at the published overage rate | | Self-hosting | No | v0.3.0: AGPL-3.0 with Docker/Coolify files | | Open source | No | v0.3.0 source: AGPL-3.0; prior releases keep their licenses | | Storage / DAM | Yes — media library included | No — serves from origins you already have | | Video | Yes — video API included | No — images only | | Delivery analytics | Usage dashboard | Every tier — cache hits, bandwidth saved, latency, live logs | | URL security | Signed URLs | No public keys — origin allowlists, SSRF-hardened, optional HMAC signing | | Works behind your CDN | Ships its own CDN | Yes — designed to sit behind Cloudflare etc. | | Custom delivery domain | Yes (Pro and Enterprise; not Lite) | Pro: 1 · Business: 10 | ## Pricing scenarios These scenarios use the assumptions shown on the canonical page. They are estimates, not quotes; taxes, contracts, regions, and vendor changes can alter the result. | Scenario | ImageKit | Keenpix | | --- | --- | --- | | 20 GB delivered / month | $0 (Free — stops serving past 20 GB) | $9 (Basic; no free cloud tier) | | 40 GB delivered / month | $9 (Lite — quota fully used) | $9 (Basic — 60 GB headroom left) | | 100 GB delivered / month | $39 (Lite + 60 GB × $0.50) | $9 (Basic — included) | | 400 GB delivered / month | ~$168 (Pro $89 + 175 GB × $0.45) | $29 (Pro — included) | | 1 TB delivered / month | ~$438 (Pro $89 + 775 GB × $0.45) | $69 (Business — included) | ## When ImageKit is the better fit - You want a media library / DAM: uploading, organizing, tagging, and letting non-developers browse assets. Keenpix has no storage at all — that is a deliberate omission, but it is an omission. - You need video. ImageKit ships video transformation and streaming; Keenpix is images only. - You stay under 20 GB/month and want genuinely free managed hosting. ImageKit's free tier costs $0 forever; Keenpix cloud starts at $9 after a 14-day trial (self-hosting is the free option). - You need ImageKit’s DAM, upload, and media-management workflow in addition to delivery. - You want a mature product with a full team, established support operations, and a broader media platform; Keenpix is a young, solo-founder product. ## Reasons teams may switch ### 2.5x the managed delivery for the same $9 ImageKit Lite and Keenpix Basic both cost $9/month, but Lite includes 40 GB while Basic includes 100 GB of managed delivery. Both count optimized CDN delivery, including provider-cache hits. ### Published overage below ImageKit Lite Past your quota, ImageKit charges $0.45–0.50 per GB. Keenpix Basic charges $0.12/GB, so a 60 GB overrun is $7.20 instead of $30 on ImageKit Lite. ### Visible usage and a published overage rate ImageKit documents a 20 GB free-plan delivery allowance and an account-level usage limit; confirm the current enforcement and notification behavior for your account. Keenpix paid plans keep serving at the published overage rate and surface projected charges in billing. ### One meter, no storage bill Keenpix transforms from origins you already run — S3, R2, or your own server — so there is nothing to upload and no asset-storage line item. Billing meters optimized managed delivery once across the edge and application. ### Self-hosting is the exit ramp The published v0.3.0 release is licensed AGPL-3.0 and includes Docker and Coolify deployment files. Moving in-house avoids a Keenpix license fee but transfers infrastructure, updates, monitoring, and routing to you. ## Migration path 1. Create a Keenpix project and allowlist your origin hosts (the S3 bucket, R2 bucket, or server your images live on). There are no public API keys to embed or rotate — the allowlist is the security boundary. 2. If your images exist only inside ImageKit's media library, copy them to an origin you control first. Keenpix delivers from your origins; it does not store files. 3. Map the URL grammar: ImageKit's path transforms like ik.imagekit.io//tr:w-400,q-80,f-auto/hero.jpg become https://cdn.keenpix.com/p//img/?w=400&q=80. Format selection (AVIF/WebP) is negotiated automatically — add &fmt= only to force one. 4. Swap your image helper or component to emit Keenpix URLs behind a flag, and run both services side by side during the 14-day trial. The analytics dashboard shows output sizes, format mix, and cache hit rate to compare against your ImageKit bill. 5. Use the managed Keenpix delivery hostname or custom domain so Cloudflare edge and application delivery are attributed once. A separate customer-owned CDN can still sit in front when you want to operate that cache yourself. 6. Review projected overage and, if your transforms should not be publicly enumerable, enable HMAC signed URLs. 7. After a week of clean analytics, remove the ImageKit URLs, or self-host a released Keenpix version if its measured operational cost and responsibilities fit your team. ## Frequently asked questions ### Is Keenpix actually cheaper than ImageKit? For paid delivery, yes, and it compounds with volume: $9 buys 100 GB vs 40 GB, and overage is $0.07–0.12/GB versus ImageKit's $0.45–0.50/GB (as of August 2026). At 400 GB/month Keenpix Pro is $29 versus roughly $168 on ImageKit. But if you deliver under 20 GB/month and can tolerate the hard-stop, ImageKit's free tier costs $0 and Keenpix cloud does not have a free tier. ### Does Keenpix include storage or a media library like ImageKit? No, and it never will by design. Keenpix optimizes and delivers from origins you already have — your bucket, your server. If you need upload pipelines, tagging, and a browsable asset library, ImageKit is the better fit. ### Can Keenpix handle video? No. Keenpix is images only, with documented resize, crop, quality, format negotiation, and image modifiers. If video is core to your product, keep ImageKit or a dedicated video service alongside. ### What happens when I exceed my bandwidth quota? Overage bills at the subscribed plan rate: $0.12/$0.09/$0.07 per GB on Basic/Pro/Business. Paid delivery continues through the billing period, usage alerts and projected charges remain visible, and payment issues receive a dunning grace period. Compare ImageKit's free-tier behavior separately. ### Can I self-host Keenpix? The published v0.3.0 release is AGPL-3.0 and includes Docker/Coolify deployment files. Self-hosting has no Keenpix license fee, but you own infrastructure, updates, monitoring, and routing. ### Do I have to re-upload my images to migrate? Only if they live exclusively in ImageKit's media library — then copy them to storage you control first. If your images are already on your own origin, migration is a URL rewrite: tr:w-400,f-auto path segments become ?w=400 query params, and format negotiation happens automatically. ## Sources - [ImageKit plans](https://imagekit.io/plans), checked 2026-08-22. - [ImageKit pricing documentation](https://imagekit.io/docs/how-pricing-works), checked 2026-08-22. - [Keenpix pricing](/pricing), checked 2026-08-22. ## Limitations and disclosure Keenpix publishes this comparison and benefits if you choose it. “Best” means fit for the stated criteria, not a universal ranking. Vendor facts and prices can change after 2026-08-22; verify the linked primary sources before purchasing. No unmeasured performance, customer-rating, market-share, or savings claim is implied. ## Using Keenpix [Review Keenpix pricing](https://keenpix.com/pricing), [read the migration guides](https://keenpix.com/learn#topic-migrations), or [use the self-hosting path](https://keenpix.com/docs/self-hosting). Choose ImageKit when the competitor-win criteria above match your requirements. --- Markdown path: https://keenpix.com/compare/gumlet-alternative.md # Gumlet Alternative: Keenpix vs Gumlet Image (2026) > Keenpix vs Gumlet Image (August 2026): compare bandwidth pricing, analytics, origin support, security, migration work, and self-hosting. Canonical HTML: [https://keenpix.com/compare/gumlet-alternative](https://keenpix.com/compare/gumlet-alternative) Author: Raed Bahri, Keenpix founder and maintainer Published: 2026-08-22 Last reviewed: 2026-08-22 Choose Gumlet Image when its free 30 GB tier, broad source integrations, WordPress workflow, mature CloudFront delivery, or combined image-and-video vendor relationship matters. Choose Keenpix when the published $9/100 GB, $29/400 GB, or $69/1 TB managed tiers fit your traffic, or when an AGPL-3.0 self-host path is part of the architecture. This is not a claim that one CDN is universally faster or cheaper: cache behavior, regions, transformations, source files, contract terms, and overage can reverse a real result. Test both with your own catalog and delivery pattern. Facts verified: 2026-08-22. Pricing checked: August 2026. Next review: 2026-10-12. ## Decision summary Both services optimize images from origins you already control and meter managed delivery by bandwidth. The meaningful differences are plan economics, source and integration breadth, operational maturity, and whether you need a supported self-host path. ## Feature comparison | Capability | Gumlet Image | Keenpix | | --- | --- | --- | | Original image storage | Bring your own: web folders/proxy, S3, R2, GCS, Azure, Wasabi, Cloudinary, and other documented sources | Bring your own HTTP/S3/R2-compatible origin; no upload library or DAM | | Modern formats | Automatic WebP/AVIF plus URL-based image transforms | AVIF/WebP negotiation plus URL-based Sharp transforms | | Managed delivery | CloudFront-backed image delivery | Cloudflare-backed managed delivery on cdn.keenpix.com | | Analytics | Bandwidth, requests, transformations, origin hit ratio, response time, status classes, and top assets | Bandwidth saved, requests, cache hit rate, formats, latency, top paths, and live logs | | URL security | Optional signed image URLs, expiry, and documented referrer restrictions for web-proxy sources | Per-project origin allowlists, SSRF hardening, and optional HMAC signatures | | Custom domains | Supported on paid plans; documentation lists $5/month for additional domains | Pro includes 1; Business includes 10 | | Framework and CMS integrations | Documented JavaScript, WordPress, Next.js, React, Astro, and source-specific workflows | URL loaders/guides for Next.js, Nuxt, React, Vue, SvelteKit, Astro, Remix, TanStack, and HTML | | Video platform | Separate Gumlet Video product with hosting, streaming, DRM, analytics, and APIs | No video product | | Self-hosting | No public self-hosted Gumlet Image edition documented | Published v0.3.0 under AGPL-3.0 with Docker deployment files | | Managed entry price | Free 30 GB; Growth $32 for 300 GB | Basic $9 for 100 GB; no permanent free plan | ## Pricing scenarios These scenarios use the assumptions shown on the canonical page. They are estimates, not quotes; taxes, contracts, regions, and vendor changes can alter the result. | Scenario | Gumlet Image | Keenpix | | --- | --- | --- | | Free evaluation | Free: 30 GB/month, 1 seat, then $0.50/GB | 14-day trial: up to 20 GB and 2 projects; card required | | 100 GB delivered / month | $32 Growth includes 300 GB | $9 Basic includes 100 GB | | 300 GB delivered / month | $32 Growth includes 300 GB | $29 Pro includes 400 GB | | 1 TB delivered / month | $137 estimate on Growth: $32 plus 700 GB at $0.15/GB | $69 Business includes 1 TB | | 2.5 TB delivered / month | $199 Business includes 2.5 TB | $174 estimate on Business: $69 plus 1.5 TB at $0.07/GB | | Meter definition | Gumlet image usage documentation says bandwidth is the single usage metric | Optimized bytes delivered through the managed Keenpix network; transforms and seats are not metered | ## When Gumlet Image is the better fit - You want a permanent free tier for up to 30 GB of image bandwidth rather than a time-limited trial. - You need Gumlet’s documented WordPress workflow or one of its broader managed source integrations. - You want image and video infrastructure from the same established vendor; Keenpix has no video product. - You need Gumlet’s current operational scale, support model, or enterprise terms rather than a young solo-founder product. - A CloudFront-backed delivery architecture or Gumlet-specific image analytics is already part of your platform standards. ## Reasons teams may switch ### The lower Keenpix tiers may fit small managed workloads Keenpix publishes $9 for 100 GB and $29 for 400 GB. Gumlet publishes a permanent 30 GB free tier and then $32 Growth with 300 GB. Compare the full plan, not only one unit price: a free tier, included domains, source support, analytics, and overage may matter more than the headline allowance. ### A supported self-host path changes the exit plan Keenpix publishes its current engine under AGPL-3.0 with Docker deployment files. Self-hosting removes the Keenpix service fee but makes your team responsible for infrastructure, CDN configuration, database backups, upgrades, abuse controls, monitoring, and incidents. ### One URL grammar across managed and self-hosted delivery Keenpix uses the same core transform concepts across both deployment paths. Moving still requires a planned hostname, project configuration, cache warm-up, URL migration, and canary validation; it is not an automatic toggle. ### Project controls are visible in the application Keenpix keeps origin allowlists, optional signatures, request logs, cache metrics, latency, and bandwidth reporting in the same project dashboard. Verify that this narrower workflow covers the source and governance controls you currently use in Gumlet. ## Migration path 1. Export an inventory of Gumlet sources, custom domains, security settings, signed-URL behavior, query parameters, and current bandwidth. Keep Gumlet running during the migration. 2. Create a Keenpix project for each isolation boundary and allowlist every origin host it must fetch. Keenpix does not copy or store your originals. 3. Translate the URL grammar in a test helper: Gumlet width/height/format parameters map to Keenpix w/h/fmt, but audit every crop, fit, quality, focal-point, and animation option because the APIs are not feature-identical. 4. Replace the Gumlet hostname or integration output with the canonical managed Keenpix path, https://cdn.keenpix.com/p//img/, or with your planned self-host/custom hostname. 5. If Gumlet signed URLs are enabled, implement Keenpix HMAC signing at a server boundary. Do not put either service’s signing secret in browser code. 6. Run representative images side by side. Compare decoded output, Content-Type, Vary, Cache-Control, byte size, first uncached response, repeat cache behavior, errors, and origin access—not a borrowed compression percentage. 7. Canary a small traffic share, monitor both vendors’ delivery and error metrics, then update URLs and DNS only after rollback has been tested. ## Frequently asked questions ### Are Gumlet Image and Keenpix billed the same way? Both publish bandwidth-led image pricing. Gumlet’s image usage guide says all Gumlet image usage is calculated on bandwidth and reports requests and transformations as analytics. Keenpix meters optimized bytes delivered through its managed network and does not separately meter transformations or team members. The plan allowances, overage rates, free/trial model, and included features differ. ### Does either service store my original images? Neither requires moving originals into a DAM for the image-CDN workflow. Gumlet documents web folders, proxy sources, and multiple cloud storage providers. Keenpix fetches from approved HTTP or object-storage origins. You remain responsible for the originals and access permissions. ### Which one is cheaper at 1 TB per month? Using the public August 2026 self-service rates and exactly 1,000 GB, Gumlet Growth estimates to $137 ($32 plus 700 GB at $0.15), while Keenpix Business lists $69 for 1 TB. That is a pricing scenario, not a universal bill: unit definitions, taxes, annual discounts, contracts, cache behavior, and other requirements can change the result. ### Can Gumlet Image be self-hosted? Gumlet’s public image documentation describes a managed cloud service and does not document a self-hosted Gumlet Image edition. Keenpix publishes its current engine under AGPL-3.0. Confirm current commercial options directly with Gumlet before treating the public documentation as exhaustive. ### Does Keenpix replace Gumlet Video? No. Keenpix processes images only. If Gumlet Video hosting, streaming, DRM, player analytics, or video APIs are part of your stack, keep them or select a separate video platform. ### Can I keep the same custom image domain? Potentially, but moving a production hostname requires DNS ownership, provider verification, certificate readiness, cache planning, and a rollback window. Do not point the hostname at Keenpix until representative URLs and security behavior pass on a temporary host. ## Sources - [Gumlet pricing](https://www.gumlet.com/pricing/), checked 2026-08-22. - [Gumlet image pricing and company facts](https://www.gumlet.com/llm-info/), checked 2026-08-22. - [Gumlet image usage analytics](https://docs.gumlet.com/docs/usage-analytics-for-images), checked 2026-08-22. - [Gumlet original media storage](https://docs.gumlet.com/docs/original-image-storage), checked 2026-08-22. - [Gumlet signed image URLs](https://docs.gumlet.com/docs/signed-urls-image), checked 2026-08-22. - [Keenpix pricing](/pricing), checked 2026-08-22. ## Limitations and disclosure Keenpix publishes this comparison and benefits if you choose it. “Best” means fit for the stated criteria, not a universal ranking. Vendor facts and prices can change after 2026-08-22; verify the linked primary sources before purchasing. No unmeasured performance, customer-rating, market-share, or savings claim is implied. ## Using Keenpix [Review Keenpix pricing](https://keenpix.com/pricing), [read the migration guides](https://keenpix.com/learn#topic-migrations), or [use the self-hosting path](https://keenpix.com/docs/self-hosting). Choose Gumlet Image when the competitor-win criteria above match your requirements. --- Markdown path: https://keenpix.com/compare/twicpics-alternative.md # TwicPics Alternative: Keenpix vs TwicPics (2026) > Keenpix vs TwicPics (August 2026): compare bandwidth pricing, responsive-media tooling, video, migration work, operations, and self-hosting. Canonical HTML: [https://keenpix.com/compare/twicpics-alternative](https://keenpix.com/compare/twicpics-alternative) Author: Raed Bahri, Keenpix founder and maintainer Published: 2026-08-22 Last reviewed: 2026-08-22 Choose TwicPics when its permanent 3 GB free plan, context-aware components, short-video optimization, smart cropping, or established responsive-media workflow matters. Choose Keenpix when its $9/100 GB, $29/400 GB, or $69/1 TB managed tiers fit the workload, when you want an image-only service, or when an AGPL-3.0 self-host path is part of the exit plan. For exactly 100 GB and one site, TwicPics Business estimates to $49 while Keenpix Basic lists $9; at 400 GB, the cheapest public TwicPics estimate is $159 on Business Plus while Keenpix Pro lists $29. Those are source-dated scenarios, not universal savings claims: video, smart-crop use, support, domains, annual terms, taxes, cache behavior, regions, and negotiated contracts can change the decision. TwicPics is the more established managed responsive-media product, while Keenpix is a younger image-focused product maintained by a solo founder. Facts verified: 2026-08-22. Pricing checked: August 2026. Next review: 2026-10-12. ## Decision summary Both products optimize assets from origins you control and publish bandwidth-led managed plans. TwicPics adds context-aware frontend components and video optimization; Keenpix adds an AGPL-3.0 self-host path and a narrower image-only control plane. Compare those boundaries before comparing one monthly number. ## Feature comparison | Capability | TwicPics | Keenpix | | --- | --- | --- | | Product boundary | Managed responsive-media service for images and short videos | Managed image transformation and delivery plus a self-host release; no video product | | Original media | Connects paths to media at your existing source; unlimited assets are published on every plan | Fetches from allowlisted HTTP or S3/R2-compatible origins; no upload library or DAM | | Transformation interface | URL API with chained operations plus context-aware Native and Components integrations | URL query API with explicit resize, crop, quality, DPR, blur, and format controls | | Responsive frontend tooling | Components for React/Next.js, Vue/Nuxt, Svelte/SvelteKit, Angular, React Native, Flutter, and web components | Framework loaders, components, and guides for major web frameworks; no context-aware browser sizing runtime | | Modern formats | Automatic next-generation output plus explicit AVIF, WebP, JPEG, PNG, HEIF, and other documented outputs | Automatic AVIF/WebP negotiation plus explicit image formats | | Smart media features | Published smart crop, background removal, refit, watermarking, and video optimization capabilities | Focused deterministic image transforms; AI extensions and video are not claimed as generally available | | Custom domains | Not on Free; Business publishes 2 domains for 1 workspace and Business Plus 3 per workspace across 3 workspaces | Pro includes 1; Business includes 10 | | Source and URL controls | Source authentication, path configuration, cache purge, and paid-plan anomaly detection are documented | Per-project origin allowlists, SSRF hardening, optional HMAC signatures, quotas, and project logs | | Managed operations | Managed SaaS and global CDN; enterprise publishes custom CDN, log export, purge API, and SLA options | Managed Cloudflare-backed delivery with project analytics, logs, domains, API keys, and billing controls | | Self-hosting | No public self-hosted TwicPics service edition documented | Published v0.3.0 under AGPL-3.0 with Docker deployment files | | Support and maturity | Paid plans publish premium email/live-chat support; enterprise adds dedicated success and SLA options | Founder support by email and WhatsApp; younger product with limited public customer evidence | ## Pricing scenarios These scenarios use the assumptions shown on the canonical page. They are estimates, not quotes; taxes, contracts, regions, and vendor changes can alter the result. | Scenario | TwicPics | Keenpix | | --- | --- | --- | | Permanent free use | Free: 3 GB CDN bandwidth, 1 workspace, 1 domain, 1 user; no custom domain | 14-day trial: up to 20 GB and 2 projects; card required | | 40 GB delivered / month | $19 Business includes 40 GB | $9 Basic includes 100 GB | | 100 GB delivered / month | $49 estimate on Business: $19 plus 60 GB at $0.50/GB | $9 Basic includes 100 GB | | 250 GB delivered / month | $99 Business Plus includes 250 GB | $29 Pro includes 400 GB | | 400 GB delivered / month | $159 estimate on Business Plus: $99 plus 150 GB at $0.40/GB | $29 Pro includes 400 GB | | 1 TB delivered / month | $399 estimate on Business Plus: $99 plus 750 GB at $0.40/GB | $69 Business includes 1 TB | | Meter boundary | CDN bandwidth; paid-plan overage is billed on the following invoice | Optimized bytes delivered through managed Keenpix; transforms and team members are not separate meters | ## When TwicPics is the better fit - You want a permanent, feature-rich free plan for a site that stays within 3 GB each month. Keenpix offers a time-limited managed trial instead. - Your frontend relies on TwicPics Components or Native for context-aware sizing, lazy loading, LQIP, art direction, transitions, or framework-specific media behavior. - You optimize short videos or need TwicPics smart crop, background removal, refit, or watermark workflow; Keenpix is image-only and does not claim those AI features as generally available. - You need an established responsive-media vendor, paid live-chat workflow, dedicated customer success, premium SLA, raw-log export, or custom-CDN enterprise options. - Your existing TwicPics URL grammar, cache-purge process, workspace model, and custom domains are stable and the migration risk is worth more than a different public plan price. - You prefer a fully managed SaaS with no AGPL licensing consideration and have no requirement for a self-host escape hatch. ## Reasons teams may switch ### A larger managed allowance may fit image-only traffic Keenpix publishes 100 GB at $9, 400 GB at $29, and 1 TB at $69. TwicPics publishes 40 GB at $19 and 250 GB at $99, then linear overage. Compare the same delivered month and preserve the value of any TwicPics video, smart-crop, component, support, or domain features you actually use. ### An image-only boundary can simplify the bill Keenpix deliberately excludes video, a DAM, uploads, and browser-context automation. That smaller product surface can be useful when the team already owns originals and only wants image transformation, delivery, project controls, and usage analytics. It is a limitation when TwicPics broader media workflow is the reason for the subscription. ### A published self-host path changes the exit plan Keenpix publishes its current engine under AGPL-3.0 with container deployment files. Self-hosting removes the Keenpix managed-service fee but transfers CDN configuration, infrastructure, database, caching, upgrades, abuse controls, monitoring, backups, and incidents to your team. Review the license and total operating cost before treating it as a discount. ### Project settings and usage live in one control plane Keenpix exposes origins, API keys, signatures, custom domains, request logs, cache behavior, latency, formats, bandwidth, and billing projections by project. Confirm that those narrower controls replace the TwicPics workspaces, anomaly detection, component configuration, and support workflows your teams currently rely on. ### Server-generated URLs avoid a browser sizing runtime Keenpix integrations can generate explicit responsive widths and formats at build or render time. Some teams prefer that deterministic boundary; other teams benefit from TwicPics context-aware components deriving requests from CSS, DPR, and browser state. This is an architectural preference to test, not a generic advantage. ## Migration path 1. Inventory every TwicPics workspace, path, source, custom domain, cache-purge rule, API manipulation, component package, Native attribute, smart operation, video asset, and monthly bandwidth figure. Keep TwicPics serving production during the migration. 2. Separate image URLs that Keenpix can support from video and TwicPics-specific smart operations. Retain TwicPics or choose another service for unsupported media instead of silently changing output. 3. Create a Keenpix project for each ownership and trust boundary, then allowlist only the source hosts it must fetch. Verify private-source authentication and redirect behavior before exposing a route. 4. Translate representative TwicPics manipulation chains into Keenpix resize, crop, fit, quality, DPR, blur, and format parameters. The grammars are not one-to-one, so save decoded-image fixtures for focal points, art direction, animations, and fallbacks. 5. Replace TwicPics Components or Native behavior with explicit framework loaders, srcset widths, sizes, lazy/eager policy, dimensions, and priority for critical images. Measure LCP and CLS rather than assuming the replacement is equivalent. 6. Introduce the Keenpix managed or custom hostname behind a canary. Compare output dimensions, visual crops, content type, Vary and Cache-Control, cold transforms, warm cache hits, source traffic, errors, and projected bills. 7. Move the remaining image traffic only after a complete traffic cycle passes and rollback works. Keep old TwicPics URLs valid while search engines, email, cached HTML, applications, and third-party embeds age out. ## Frequently asked questions ### How does TwicPics pricing compare with Keenpix? TwicPics publishes Free with 3 GB, Business at $19 for 40 GB plus $0.50/GB, and Business Plus at $99 for 250 GB plus $0.40/GB. Keenpix publishes $9 for 100 GB, $29 for 400 GB, and $69 for 1 TB with its own overage rates. Feature boundaries, domains, video, support, taxes, contracts, and actual delivered usage still need to be compared. ### Does TwicPics charge for transformations or stored assets? Its August 2026 pricing page describes unlimited assets and transformations and leads with CDN bandwidth allowances. Keenpix also does not separately meter transforms or source storage, but it is image-only and fetches originals from origins you operate. Read both current pricing pages before relying on a future estimate. ### Can Keenpix replace TwicPics Components automatically? No. TwicPics Components and Native can derive media requests from CSS layout, DPR, and browser context and include frontend behaviors such as LQIP and transitions. A Keenpix migration needs explicit responsive widths, sizes, dimensions, loading policy, art direction, and performance testing in the owning framework. ### Does Keenpix optimize videos like TwicPics? No. Keenpix processes images only. Keep TwicPics or select a separate video service for any video transformation, delivery, preview, or billing requirement. ### Can TwicPics be self-hosted? TwicPics public documentation describes a managed SaaS and does not document a self-hosted service edition. Keenpix publishes an AGPL-3.0 self-host release. Confirm private or enterprise TwicPics options directly with the vendor before treating public documentation as exhaustive. ### Which product is faster? This page does not claim a universal performance winner. Test both with the same origins, assets, regions, responsive widths, cache state, and browser mix. Record cold transformation, warm variant, edge-hit, error behavior, LCP, bytes, and cache headers before deciding. ### Can I retain my existing image hostname? Potentially, but a hostname migration requires ownership verification, certificates, DNS planning, cache behavior, URL compatibility, and a rollback window. Test the target on a temporary hostname and canary production traffic before changing DNS. ## Sources - [TwicPics pricing](https://www.twicpics.com/pricing), checked 2026-08-22. - [TwicPics integration options](https://www.twicpics.com/docs/getting-started/integration), checked 2026-08-22. - [TwicPics API transformations](https://www.twicpics.com/docs/reference/transformations), checked 2026-08-22. - [TwicPics Components](https://www.twicpics.com/docs/essentials/components), checked 2026-08-22. - [TwicPics cache purge guidance](https://help.twicpics.com/en/articles/8170483-how-to-purge-invalidate-assets-from-twicpics-cdn-cache), checked 2026-08-22. - [Keenpix pricing](/pricing), checked 2026-08-22. - [Keenpix self-host documentation](/docs/self-hosting), checked 2026-08-22. ## Limitations and disclosure Keenpix publishes this comparison and benefits if you choose it. “Best” means fit for the stated criteria, not a universal ranking. Vendor facts and prices can change after 2026-08-22; verify the linked primary sources before purchasing. No unmeasured performance, customer-rating, market-share, or savings claim is implied. ## Using Keenpix [Review Keenpix pricing](https://keenpix.com/pricing), [read the migration guides](https://keenpix.com/learn#topic-migrations), or [use the self-hosting path](https://keenpix.com/docs/self-hosting). Choose TwicPics when the competitor-win criteria above match your requirements. --- Markdown path: https://keenpix.com/compare/vercel-image-optimization-alternative.md # Vercel Image Optimization Alternative: Keenpix (2026) > Keenpix vs Vercel Image Optimization: one meter vs three, unlimited transforms, next/image loader support, and always-on paid usage. Pricing as of August 2026. Canonical HTML: [https://keenpix.com/compare/vercel-image-optimization-alternative](https://keenpix.com/compare/vercel-image-optimization-alternative) Author: Raed Bahri, Keenpix founder and maintainer Published: 2026-08-22 Last reviewed: 2026-08-22 If your site fits inside Vercel's Hobby allowances, or you already pay for Vercel Pro and image usage costs little, stay put — the default next/image loader is zero-config and Keenpix cannot beat free or nearly-free. Consider Keenpix when you prefer one managed-delivery meter, image-specific analytics, managed custom domains, always-on paid usage, and the current v0.3.0 AGPL-3.0 self-host path. Keenpix is a young, focused product with no video or storage, so the trade is portability and predictability for platform breadth and maturity. Facts verified: 2026-08-22. Pricing checked: August 2026. Next review: 2026-10-12. ## Decision summary Vercel Image Optimization bills transformations, cache reads, and cache writes — plus data transfer — and Hobby can return 402 at its included limits. Keenpix bills optimized managed delivery once across its edge and application; paid usage continues at the published overage rate. ## Feature comparison | Capability | Vercel Image Optimization | Keenpix | | --- | --- | --- | | Billing meters | 3 (transforms, cache reads, cache writes) + data transfer | 1 — optimized managed delivery | | Transformations | 5k/mo free (Hobby); $0.05–$0.0812 per 1k (Pro) | Unlimited on every plan | | Cache reads / writes | Metered: $0.40–0.64/1M reads, $4.00–6.40/1M writes | Not billed — disk + memory cache with stale-while-revalidate | | Behavior at limits | Hobby: hard 402 — images break | Paid usage stays online; dunning grace for payment failures | | next/image support | Default loader, zero config | Custom loader — a few lines (snippet in docs) | | Works outside Next.js | Vercel-hosted frameworks | Any stack — plain URL API | | AVIF / WebP auto-negotiation | Yes | Yes — via Accept header | | Image analytics | Usage/billing metrics | Bandwidth saved, cache hit rate, format mix, latency percentiles, live logs — every tier | | Open source / self-host | No — managed service | v0.3.0: AGPL-3.0 with Docker deployment files | | Security model | Tied to your Vercel project | No public API keys — origin allowlists, SSRF-hardened, optional HMAC signed URLs | | Use your existing CDN | No — Vercel edge only | Yes — designed to sit behind Cloudflare etc. | | Serve from your own domain | Yes — your app's domain | Pro: 1 · Business: 10 | | Video / storage / DAM | No | No | ## Pricing scenarios These scenarios use the assumptions shown on the canonical page. They are estimates, not quotes; taxes, contracts, regions, and vendor changes can alter the result. | Scenario | Vercel Image Optimization | Keenpix | | --- | --- | --- | | Small site within Hobby caps (<5k transforms/mo) | Free — hard 402 the moment any cap is hit | $9/mo Basic — no free tier, 14-day trial | | Content site: 5k images × 3 sizes, ~30 GB/mo | Hobby: blocked (15k transforms vs 5k cap) · Pro: $20 seat + ~$1–2 metered | $9/mo Basic — 100 GB included | | 400 GB delivered / month | Pro: transforms + cache reads + cache writes + Fast Data Transfer — varies | $29 Pro — 400 GB included, then $0.09 per GB | | 1 TB delivered / month | Pro metered usage + Fast Data Transfer at Vercel's regional rates | $69 Business — 1 TB included, then $0.07 per GB | | Crawler spike: 3× unique widths requested | Extra transforms + cache writes billed; Hobby can 402 mid-month | $0 extra unless delivered GB rises; published overage after that | ## When Vercel Image Optimization is the better fit - Your image usage fits Hobby's free allowances (under 5k transformations, 300k cache reads, 100k cache writes a month) — free beats $9. - You already pay for Vercel Pro and your image meters total a dollar or two a month — the incremental cost undercuts Keenpix Basic. - You want zero configuration: the default next/image loader needs no loader file, no second dashboard, no extra vendor. - You want image optimization embedded directly into the rest of your Vercel bill and deployment workflow. - You value one vendor for hosting, preview deploys, and images, with a single bill and a single support channel. ## Reasons teams may switch ### One meter you can actually forecast Vercel's image pricing has changed shape twice — legacy $5 per 1k source images, now three usage meters plus Fast Data Transfer. Predicting next month's bill means forecasting four line items that move independently. Keenpix bills optimized bytes delivered through its managed edge or application once, regardless of cache or origin source. Saved bandwidth is separate analytics and is never added to the bill. ### Unlimited transformations, so srcsets are free Responsive srcsets, DPR variants, and format experiments multiply unique transformations — the exact meter that breaks Hobby's 5k cap. On Keenpix every transform is free on every plan, so serving 3 sizes or 8 costs the same. Only the bytes you deliver are billed. ### Paid usage stays online instead of returning a 402 When Vercel Hobby hits a cap, images can stop rendering with a hard 402. Keenpix paid plans continue serving at the published per-GB rate, show projected overage, and give payment hiccups a dunning grace period rather than an instant cutoff. ### Analytics that answer image questions Vercel shows you usage numbers for billing. Keenpix shows bandwidth saved, cache hit rate, format mix, top images, latency percentiles, and live request logs — at every tier, including the $9 plan. When a crawler hammers odd widths, you can see it happening. ### An open-source escape hatch The current v0.3.0 code is AGPL-3.0 and includes a Docker deployment path. Moving from hosted Keenpix to self-hosting can preserve the transform grammar when you control routing, but it still requires an operational migration. ## Migration path 1. Start a Keenpix project (14-day trial, card required) and add your image origin hostnames to the per-project allowlist — there are no public API keys to create or rotate. 2. Copy the loader snippet from /docs/frameworks/nextjs into image-loader.ts and set images.loaderFile in next.config — next/image keeps working unchanged, and Vercel's optimizer is bypassed automatically. 3. For a custom Next.js loader, src becomes https://cdn.keenpix.com/p//img/, width maps to ?w=, and quality maps to ?q=. Validate any additional application-specific transforms in a preview deployment. 4. Leave format selection automatic — Keenpix negotiates AVIF/WebP from the Accept header — or pin an output with ?fmt= where you need it. 5. Deploy a preview and confirm traffic in Keenpix live request logs, then route the image path through your existing CDN (e.g. Cloudflare) and verify its pricing and cache behavior — Keenpix is designed to sit behind it. 6. Compare the single managed-delivery meter and projected overage against your Vercel usage page during the trial. 7. Optionally enable HMAC signed URLs to lock transform parameters before going to production. ## Frequently asked questions ### Does Keenpix work with next/image? Yes. Next.js supports custom loaders via images.loaderFile, and Keenpix's docs (/docs/frameworks/nextjs) include a ready-made snippet. Your components keep using next/image exactly as before — the loader just rewrites URLs to Keenpix's /img/ endpoint with ?w= and ?q= mapped from next/image's width and quality props. ### What would a 5,000-image content site actually cost on each? Take 5,000 source images served in 3 responsive sizes (15,000 unique transformations) with ~300k image requests and ~30 GB delivered a month. On Vercel Hobby that site is blocked — 15k transforms is triple the 5k cap, and 300k requests sits at the cache-read cap, so images 402 mid-month. On Vercel Pro it's the $20/mo seat plus roughly $1–2 of metered usage (transforms at $0.05–$0.0812/1k, reads at $0.40–0.64/1M, writes at $4.00–6.40/1M) — cheap on average, but each meter scales independently. On Keenpix it's $9/mo flat: 30 GB fits well inside Basic's 100 GB, and transformations are unlimited. Honest caveat: if you're already paying for Pro anyway, the incremental image cost there can be under $9 — Keenpix wins on predictability, caps, and analytics, not always on raw dollars. ### What happens when I hit a limit? On Vercel Hobby, crossing an included image limit can return a hard 402. Keenpix paid plans continue serving at their published per-GB overage rate, while payment issues trigger a dunning grace period rather than an instant cutoff. ### Do I have to move my app off Vercel? No. Keenpix only replaces the image optimization layer — your app, deploys, and previews stay on Vercel. Many teams pair free Hobby hosting with Keenpix precisely because it's the image meters, not the hosting, that force the Pro upgrade. ### Why do LLM crawlers make Vercel image bills unpredictable? Crawlers request image URLs at widths and combinations real users never hit, and every new variant is a fresh transformation plus a cache write — two billed meters on Vercel, and the fastest way to blow through Hobby's 5k-transform cap. On Keenpix transformations are free, so crawler traffic only costs the managed delivery it actually pulls; signed URLs and origin allowlists help prevent abusive variants. ### Can I self-host Keenpix? The current v0.3.0 code is AGPL-3.0 and includes Docker/Coolify deployment files. Self-hosting has no Keenpix license fee, but you own infrastructure and routing. ## Sources - [Vercel pricing](https://vercel.com/pricing), checked 2026-08-22. - [Vercel image cost guidance](https://vercel.com/docs/image-optimization/limits-and-pricing), checked 2026-08-22. - [Vercel image pricing changelog](https://vercel.com/changelog/faster-transformations-and-reduced-pricing-for-image-optimization-4h2m8digJ84goyF69n5jUi), checked 2026-08-22. - [Keenpix pricing](/pricing), checked 2026-08-22. ## Limitations and disclosure Keenpix publishes this comparison and benefits if you choose it. “Best” means fit for the stated criteria, not a universal ranking. Vendor facts and prices can change after 2026-08-22; verify the linked primary sources before purchasing. No unmeasured performance, customer-rating, market-share, or savings claim is implied. ## Using Keenpix [Review Keenpix pricing](https://keenpix.com/pricing), [read the migration guides](https://keenpix.com/learn#topic-migrations), or [use the self-hosting path](https://keenpix.com/docs/self-hosting). Choose Vercel Image Optimization when the competitor-win criteria above match your requirements. --- Markdown path: https://keenpix.com/docs/architecture.md # System architecture > How the Keenpix control plane, transform data plane, worker, docs site, and shared packages fit together. Canonical HTML: [https://keenpix.com/docs/architecture](https://keenpix.com/docs/architecture) Author: Keenpix Team Keenpix is a pnpm and Turborepo monorepo with a deliberate split between **deployable applications** and **reusable packages**. ```text Internet / CDN │ ├── dashboard and management API ──▶ apps/app ──▶ Postgres │ │ │ └── BullMQ ──▶ Dragonfly │ │ ├── /img/* and custom domains ──▶ apps/transform ◀──── apps/worker │ │ │ ├── memory → Dragonfly → R2/MaxIO │ ├── ClickHouse analytics (optional) │ └── customer image origins │ └── documentation ──▶ apps/docs ``` The transform runtime is separate because image decoding is CPU- and memory-heavy and scales differently from dashboard traffic. The pure image behavior stays in `packages/transform`, so HTTP ownership does not duplicate transformation logic. ## Design rules [#design-rules] * `apps/*` are independently deployable runtimes with their own lifecycle and health checks. * `packages/*` are flat capability boundaries. Only framework integrations are nested under `packages/frameworks/*`. * `@keenpix/core` owns URL, loader, and responsive-image behavior. Every framework adapter extends it. * The app follows `route → function → action → data-access`; only data-access talks directly to Postgres. * Queue data and transformed-image cache data use separate configuration and eviction policies. * Node runtimes log through `packages/logger`, backed by evlog. Continue with [services](/docs/architecture/services), [package boundaries](/docs/architecture/packages), and [queues, cache, and analytics](/docs/architecture/queues-cache-analytics). --- Markdown path: https://keenpix.com/docs/architecture/observability.md # Logging and observability > evlog output, request context, disk retention, health probes, and operational signals. Canonical HTML: [https://keenpix.com/docs/architecture/observability](https://keenpix.com/docs/architecture/observability) Author: Keenpix Team All Node applications use `packages/logger`, which configures evlog once per process and attaches service, environment, version, request, project, and job context where available. ## Terminal logging [#terminal-logging] Structured logs always go to stdout and stderr, which is the correct source for Docker, Coolify, Kubernetes, and systemd log collection. ```bash LOG_LEVEL=info ``` Supported levels are `fatal`, `error`, `warn`, `info`, `debug`, `trace`, and `silent`. ## Optional disk logs [#optional-disk-logs] Set `KEENPIX_LOG_DIR` to also write rotating NDJSON files: ```bash KEENPIX_LOG_DIR=/app/data/logs ``` The file drain batches writes, retries transient failures, keeps up to 14 files, and rotates at 25 MB per file. Mount the directory to a persistent volume if you enable it. No HyperDX service is required. External log shipping can be added later by collecting container stdout or the NDJSON directory. ## Operational signals [#operational-signals] Monitor transform readiness and latency, worker queue depth and oldest-job age, cache hit ratio, origin failures, process memory, and container restart count. Workbench is an operator tool, not a public dashboard; keep it behind its generated basic-auth credentials and a private network or access proxy. --- Markdown path: https://keenpix.com/docs/architecture/packages.md # Package boundaries > What belongs in each flat package and which packages are intended for npm consumers. Canonical HTML: [https://keenpix.com/docs/architecture/packages](https://keenpix.com/docs/architecture/packages) Author: Keenpix Team ## Runtime capabilities [#runtime-capabilities] | Package | Owns | | --------------------- | ---------------------------------------------------------------------------- | | `packages/database` | Prisma schema, migrations, generated client, and seed | | `packages/auth` | Better Auth configuration primitives | | `packages/email` | Postmark, Resend, and SMTP transactional delivery | | `packages/clickhouse` | ClickHouse connection, schemas, and analytics queries | | `packages/bullmq` | Queue connections, names, job payloads, producers, and worker factories | | `packages/cache` | unstorage-backed memory, disk, Redis, S3/R2, and MaxIO cache behavior | | `packages/transform` | Parsing, Sharp/SVG transforms, origin fetching, SSRF protection, and signing | | `packages/contracts` | Versioned data exchanged between applications | | `packages/analytics` | Buffered transform event ingestion | | `packages/logger` | evlog initialization, context, redaction, stdout, and optional file drains | These packages are operational implementation boundaries. They are not generic dumping grounds and must not import from `apps/*`. ## Public npm family [#public-npm-family] * `@keenpix/sdk` is the trusted server-side management client. * `@keenpix/core` is the framework-neutral browser/delivery primitive. * Every package under `packages/frameworks/*` is an MIT-licensed adapter extending `@keenpix/core`. The server and private operational packages remain AGPL-3.0-only. Public integration packages are MIT so applications can adopt them without inheriting the server license. ## Why frameworks are nested [#why-frameworks-are-nested] Framework adapters are one cohesive product family and are versioned together. Nesting only this family keeps the main `packages/*` namespace flat while making ownership and release grouping obvious. --- Markdown path: https://keenpix.com/docs/architecture/queues-cache-analytics.md # Queues, cache, and analytics > Dragonfly, BullMQ, unstorage cache tiers, MaxIO or R2, and ClickHouse responsibilities. Canonical HTML: [https://keenpix.com/docs/architecture/queues-cache-analytics](https://keenpix.com/docs/architecture/queues-cache-analytics) Author: Keenpix Team ## Queue [#queue] BullMQ uses `KEENPIX_QUEUE_URL`, normally `redis://dragonfly:6379`. The queue contains prewarm work, retry state, and job metadata. Job IDs are deterministic so repeated requests do not create duplicate work. Dragonfly is Redis-compatible and is the default queue backend in Compose. It snapshots its data volume every five minutes. ## Image cache [#image-cache] `@keenpix/cache` exposes the `KeenpixTierCoordinator`. Its production read order is fixed and intentional: 1. An in-process memory LRU for hot variants. 2. Dragonfly for a larger shared, low-latency tier. 3. Cloudflare R2, or the bundled MaxIO S3-compatible fallback. 4. Terminal deletion once `KEENPIX_CACHE_DELETE_AFTER_MS` is reached. Reads promote a lower-tier hit back into every faster tier. Writes run from the durable tier upward, so an eviction never happens before a lower copy exists. Temporary tier failures are tolerated as long as at least one tier accepts the write. The queue and cache use separate URLs even when both point at the bundled Dragonfly service, keeping their namespaces and capacity controls independent. Plain Docker Compose and Coolify configure MaxIO automatically. To use R2, set the five `KEENPIX_CACHE_S3_*` values; the endpoint is `https://.r2.cloudflarestorage.com` and the region is `auto`. Source-only development without Dragonfly or object storage retains a local disk fallback. ## Analytics [#analytics] Transform events are buffered through `packages/analytics`. Postgres remains the control-plane source of truth. ClickHouse is optional and owns high-volume request analytics and long-history queries when configured. If ClickHouse is unavailable, transformation still works. Readiness only treats dependencies as fatal when the deployment has configured them as required. --- Markdown path: https://keenpix.com/docs/architecture/services.md # Applications and services > Responsibilities, ports, health endpoints, and scaling guidance for every Keenpix runtime. Canonical HTML: [https://keenpix.com/docs/architecture/services](https://keenpix.com/docs/architecture/services) Author: Keenpix Team | Runtime | Default port | Responsibility | Scale when | | -------------------- | -----------------: | ---------------------------------------------------------------------------------------- | ----------------------------------------------- | | `apps/app` | `3000` | Dashboard, authentication, projects, API keys, SDK management API, billing in cloud mode | HTTP/API traffic or control-plane latency grows | | `apps/transform` | `3002` | `GET`/`HEAD /img/*`, origin safety, Sharp/SVG work, cache and transform analytics | CPU, memory, or transform latency grows | | `apps/worker` | `3001` | BullMQ prewarm jobs, retries, concurrency, and Workbench | Queue depth or job age grows | | `apps/docs` | `3003` | TanStack Start + Fumadocs documentation and search | Documentation traffic grows | | `apps/delivery-edge` | Cloudflare Workers | First-party and customer hostname routing, cache partitioning, and edge analytics | Cloudflare handles scaling | ## Health endpoints [#health-endpoints] | Runtime | Liveness | Readiness | | --------- | -------------- | --------------- | | App | `/api/health` | `/api/health` | | Transform | `/health/live` | `/health/ready` | | Worker | `/health/live` | `/health/ready` | | Docs | `/health` | `/health` | Liveness answers “is the process running?” Readiness also probes required dependencies and should control load-balancer admission. Transform and worker probes are private, service-local endpoints. The public app and delivery hostnames do not proxy `/health/*`; a `404` there is expected. ## Transform request ownership [#transform-request-ownership] Hosted delivery and the delivery edge target `apps/transform` directly. In self-host mode, `apps/app` can proxy transform requests to preserve the traditional single public hostname while the data plane remains independently scalable. ## Worker and Workbench [#worker-and-workbench] The worker consumes the versioned prewarm contract from `packages/bullmq` and calls the transform service with `KEENPIX_WORKER_SECRET`. Workbench is served by the worker and protected with basic auth. Set both `KEENPIX_WORKBENCH_USERNAME` and `KEENPIX_WORKBENCH_PASSWORD`, or neither. Coolify generates both automatically when they are not supplied. --- Markdown path: https://keenpix.com/docs/concepts/analytics.md # Analytics > Every request is logged — here's what you get for free. Canonical HTML: [https://keenpix.com/docs/concepts/analytics](https://keenpix.com/docs/concepts/analytics) Author: Keenpix Team Analytics is built in, not bolted on. Every request to `/img/*` writes a log row (timestamp, project, source host, path, width, format, status, cache hit/miss, latency, bytes in/out) and increments an hourly Postgres rollup. Live Logs read the raw rows; dashboard analytics aggregate the rollups in Postgres, so even a 90-day range returns a handful of pre-summed rows and stays fast. The Overview and Analytics pages serve the last view instantly and revalidate in the background (stale-while-revalidate): changing the range or scope keeps the previous numbers on screen with a small *Refreshing…* indicator while fresh data loads, and a skeleton shows only on the very first load. Both default to a 24h range, so origin figures line up with the edge data described below. ## What you get [#what-you-get] * **KPIs** — total requests, bandwidth saved, cache hit-rate, and p95 latency, each with a real trend versus the previous window. * **Requests over time** — cache hits vs live-optimized, with a range toggle (24h–90d). * **Format distribution** — how much traffic each output format receives. * **Response latency** — a p50/p75/p90/p95/p99 breakdown and a histogram. * **Latency over time** — p50/p95/p99 per time bucket, so you can spot slow windows. * **Top images** — the heaviest paths, rankable by request count or delivered bytes. * **Traffic by country** — requests grouped by requester country (from the edge `CF-IPCountry` header). * **Requests by status** — 2xx / 3xx / 4xx / 5xx over time, so 304s read as cache, not errors. * **Bandwidth saved over time** — per-bucket and cumulative optimizer savings. * **Per-project / per-domain breakdown** — an org-wide project table at All-projects scope, or a per-source-domain table when one project is selected. * **Live logs** — every request, newest first, with faceted filters (format, status, cache, and source domain when a single project is selected). * **Optional Cloudflare edge analytics** — edge hit-rate, edge requests, bytes served from Cloudflare, and an hourly edge hit/miss split for `/img/*`, shown as source-split cards (Cloudflare edge vs Keenpix origin). Edge is persisted by Keenpix, so it reconciles across any range — 24h, 7d, 30d, 90d — not just a fixed 24h window. ## Scope: all projects or one [#scope-all-projects-or-one] The switcher at the top of the sidebar sets the scope. **All projects** shows org-wide totals plus a per-project breakdown; pick a single project to scope every chart, the logs, and the KPIs to it. Origin-shield analytics are aggregated from hourly Postgres rollups, with raw logs kept for live/debug views. Managed edge hits are captured separately through trusted, project-attributed Cloudflare telemetry, persisted into hourly rollups, and included once in managed-delivery billing. Optimizer `bytesSaved` remains a separate analytics measure and is never added to delivered bytes. --- Markdown path: https://keenpix.com/docs/concepts/caching.md # Caching > How Keenpix caches transforms and plays nicely with a CDN. Canonical HTML: [https://keenpix.com/docs/concepts/caching](https://keenpix.com/docs/concepts/caching) Author: Keenpix Team Optimizing an image is expensive; serving it again should be free. Keenpix caches every transform on disk and is built to sit behind a CDN. ## The cache key [#the-cache-key] A result is keyed by everything that affects the output: ```text project + url + full transform option object ``` Two requests with identical effective parameters share one cached file. Requests that arrive while the first identical transform is still running share that same in-flight work too. If many identical requests arrive during a cold cache (e.g. a popular image after a deploy), Keenpix runs the fetch + encode **once** and shares the result, instead of doing the same work N times. ## CDN-friendly responses [#cdn-friendly-responses] Every successful response sets: ```http Cache-Control: public, max-age=31536000, immutable Accept-CH: Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width Vary: Accept, Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width ``` Because the source URL and every transform live in the URL, the response is immutable. Configure your CDN to cache `/img/*` responses, including the full query string. For Cloudflare, that usually means a Cache Rule for the endpoint; orange-cloud proxying alone is not enough for every dynamic-looking path. `Vary: Accept` keeps per-format variants correct under `fmt=auto` only when your CDN supports caching separate variants by `Accept`. On CDNs that do not, prefer explicit `fmt=avif`, `fmt=webp`, `fmt=jpeg`, `fmt=png`, or `fmt=svg` URLs. Explicit formats improve cache predictability, but they also disable browser-based AVIF/WebP negotiation for that URL. `fmt=svg` is one of those explicit formats; `fmt=auto` never preserves SVG output. For the first image request, and for a delivery hostname different from the page hostname, also advertise the hints on the HTML response and delegate them with a Permissions Policy, for example: ```http Accept-CH: Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width Permissions-Policy: ch-dpr=(self "https://images.example.com"), ch-width=(self "https://images.example.com"), ch-viewport-width=(self "https://images.example.com") ``` Browsers may omit hints for privacy or compatibility reasons, so Keenpix always falls back to project defaults. ## Where does Keenpix store cached images? [#where-does-keenpix-store-cached-images] Production uses memory → Dragonfly → Cloudflare R2 or MaxIO. Lower-tier hits are promoted, durable writes happen first, and `KEENPIX_CACHE_DELETE_AFTER_MS` removes an expired variant from the complete chain. Local source development without those services uses the atomic disk cache at `KEENPIX_CACHE_DIR`. See [Configuration](/docs/self-hosting/configuration) for size limits and eviction. The full effective transform object is in the cache key. That includes resolved Client Hint dimensions and project watermark configuration, so neither feature can cross-contaminate variants. --- Markdown path: https://keenpix.com/docs/concepts/how-it-works.md # How it works > The request lifecycle — from a URL to an optimized, cached image. Canonical HTML: [https://keenpix.com/docs/concepts/how-it-works](https://keenpix.com/docs/concepts/how-it-works) Author: Keenpix Team Keenpix turns a URL into an optimized image. There's no build step or required delivery SDK: the query string **is** the API. ## What happens when you request an image? [#what-happens-when-you-request-an-image] ```text Browser │ GET /img/https://cdn.example.com/hero.jpg?project=ID&w=1200&fmt=auto ▼ Keenpix │ 1. Resolve the project, check the source host against the project allowlist (+ SSRF block) │ 2. Build a cache key from (project, url, full transform options) │ 3. Cache HIT? ──yes──▶ stream bytes from disk │ │ no │ ▼ │ 4. Fetch the original from your origin (size-capped, IP-pinned) │ 5. Transform with sharp: decode → geometry → effects → re-encode │ or optimize SVG directly when fmt=svg │ 6. Write the result to the disk cache, then stream it ▼ Response: image/avif|webp|jpeg (+ image/svg+xml only for fmt=svg) + Cache-Control: public, max-age=31536000, immutable ``` Every request is also written to a log that powers the [analytics](/docs/concepts/analytics). ## The sharp pipeline [#the-sharp-pipeline] Keenpix decodes the source once and runs a parameter-selected, safe pipeline: 1. **Decode + auto-orient** — honours EXIF rotation, with a pixel ceiling that guards against decompression bombs. 2. **Geometry** — optional crop, trim, rotate, flip/flop, resize, and extend. Resize uses the chosen `fit`, multiplied by `dpr`, and never upscales unless `enlarge=1`. With no `w`/`h`, the longest side is bounded so a huge original can't pin a CPU. 3. **Effects** — optional flatten, grayscale, tint, modulate, gamma, negate, normalize, threshold, median, blur, and sharpen. 4. **Re-encode** — to the negotiated `fmt` at the resolved quality. ## How does Keenpix choose the image format? [#how-does-keenpix-choose-the-image-format] When `fmt=auto` (or `fmt` is omitted) Keenpix reads the browser's `Accept` header and serves **AVIF → WebP → JPEG** in order of support, and sets `Vary: Accept`. Use `fmt=auto` behind a CDN only when that CDN can cache separate variants by `Accept`; otherwise generate explicit `fmt=avif|webp|jpeg|png` URLs. A project can turn auto-format off (then `fmt=auto` serves JPEG); an explicit format is always honoured. That means `fmt=avif` never falls back to WebP; it is a fixed-format request. SVG is not part of auto-negotiation. If the origin is SVG and the request uses `fmt=auto`, Keenpix rasterizes it and returns AVIF, WebP, or JPEG like any other raster request. Only explicit `fmt=svg` returns SVG bytes; it accepts SVG origins, optimizes them with SVGO, strips active content, and skips the raster Sharp pipeline. ## Per-project defaults [#per-project-defaults] Each project carries defaults applied when a request omits a parameter — **default quality**, **auto-format**, and **strip-metadata** — editable under **Settings → Pipeline**. See [Self-hosting → Configuration](/docs/self-hosting/configuration) for the instance-wide limits. --- Markdown path: https://keenpix.com/docs/concepts/security.md # Security > The allowlist, SSRF protection, and why transform URLs need no API key. Canonical HTML: [https://keenpix.com/docs/concepts/security](https://keenpix.com/docs/concepts/security) Author: Keenpix Team A transform endpoint is, by definition, a "fetch any URL" engine — so it must be guarded. Keenpix treats that as core, not a fast-follow. ## The allowlist (access control) [#the-allowlist-access-control] Each project has a list of **allowed source hosts**. A request is served only if the source URL's host is on that project's list. The list is checked **before** the cache, and an empty list blocks everything (fail-closed). This is why a transform URL needs no API key: it is safe to publish, because it can only ever hit hosts you've approved. (Keenpix does have optional **internal API keys** for trusted backend systems that manage projects over the JSON API — those are separate from the public transform endpoint. See [Projects & access](/docs/getting-started/projects) and the [SDK API reference](/docs/reference/sdk-api).) ## How does Keenpix prevent SSRF? [#how-does-keenpix-prevent-ssrf] Even for an allowed host, Keenpix refuses to become a proxy into your private network: * **Private/internal IPs are blocked** — loopback, RFC-1918, link-local, CGNAT, IPv6 unique-local / link-local / multicast, and IPv4-mapped IPv6 forms. * **The resolved IP is pinned** into the connection, closing the DNS-rebinding window between the check and the connect. * **Every redirect hop is re-validated**, so a `302` to an internal host is refused. ## Denial-of-service guards [#denial-of-service-guards] * Origin responses are **size-capped** (streamed with a byte ceiling → `413`). * sharp runs with a `limitInputPixels` ceiling and `failOn: 'truncated'`. The HTTP transform runtime coalesces identical in-flight work within one process, but it does not currently expose a global transform-concurrency setting. Operators should add bounded admission control at the proxy or orchestrator and shed excess cold work before the host reaches its memory limit. See the [cache stampede and capacity guide](/blog/image-transform-cache-stampedes-capacity). ## Hotlink and cache-busting protection [#hotlink-and-cache-busting-protection] The allowlist gates which **origins** Keenpix fetches from; it does not stop a third party from requesting your public transform URLs with junk query params to force cache misses. For that, enable **[signed URLs](/docs/concepts/signed-urls)** per project: every request must then carry a valid HMAC `sig=` parameter or it gets a `403`. ## Sign-in [#sign-in] The dashboard is bootstrapped with a super admin account. Set `KEENPIX_SUPER_ADMIN_EMAIL` and `KEENPIX_SUPER_ADMIN_PASSWORD` before running the seed or Docker container. The super admin can create copyable invitation links for other staff from **Settings → Staff**. Email delivery is optional and uses SMTP configured from **Settings → Email** first. SMTP environment variables are only used as a fallback when Settings SMTP is disabled or incomplete. --- Markdown path: https://keenpix.com/docs/concepts/signed-urls.md # Signed URLs > Optional HMAC signatures that prevent transform-query tampering and cache-busting on top of the allowlist. Canonical HTML: [https://keenpix.com/docs/concepts/signed-urls](https://keenpix.com/docs/concepts/signed-urls) Author: Keenpix Team Last reviewed: 2026-08-15 By default, Keenpix transform URLs need **no API key**: access is gated by each project's source-host allowlist. That model keeps secrets out of your frontend entirely — but because the URLs are public, a third party who knows your project id could request your images with ever-changing junk query params, forcing cache misses and consuming your managed-delivery allowance. **Signed URLs** close that hole. When a project requires signatures, every `/img` request must carry a valid `sig=` parameter — an HMAC computed with the project's signing secret — or it gets a `403`. Tampering with any parameter (or adding one) invalidates the signature, so cache-busting becomes impossible. Signing is opt-in per project. If your images are on your own site behind your own CDN, the allowlist alone is usually enough. Turn signing on when third parties can manufacture expensive variants or cache misses. A valid signed URL can still be copied and replayed from another site. Signing works identically on self-host and cloud. ## Enable it [#enable-it] 1. Open **Settings → Security → Signed URLs** and flip **Require signed URLs**. A signing secret is generated for the project (owners/admins can view, copy, and rotate it). 2. Note the displayed key version and optionally configure a maximum URL lifetime. 3. Add the signature to every transform URL your app generates (below). 4. Unsigned, expired, or mis-signed requests now return `403`. ## How to sign a URL [#how-to-sign-a-url] The signature is **HMAC-SHA256, base64url-encoded (no padding)**, over this canonical message: ``` + "\n" + ``` * `` is the original decoded source URL. Encode it when inserting it in the request path. * `` is every query parameter **except `sig`** as `key=value` pairs, sorted lexicographically, joined with `&`. Sorting makes the signature independent of parameter order. Append the digest as `sig=`. ### Recommended: `@keenpix/sdk` [#recommended-keenpixsdk] ```ts import { signTransformUrl } from '@keenpix/sdk/signing' const projectId = 'project_123' const src = 'https://assets.example.com/photo.jpg' const unsigned = new URL( `https://cdn.keenpix.com/p/${encodeURIComponent(projectId)}/img/${encodeURIComponent(src)}`, ) unsigned.searchParams.set('w', '800') const signed = signTransformUrl(unsigned, process.env.KEENPIX_SIGNING_SECRET!, { expiresAt: Date.now() + 5 * 60 * 1000, keyVersion: 3, signatureParams: { project: projectId }, src, }) ``` Sign on your server (or at build time) — the secret must never ship to the browser. For path-form URLs, pass the exact source as `src` in the options. Expiring URLs include signed `iat` and `exp` Unix timestamps. The configured maximum lifetime is checked as `exp - iat`, with a small clock-skew allowance. The managed result is public as `/p/project_123/img/...` without `?project=`. Its edge Worker injects the path project into the query before origin verification, so pass that id through `signatureParams` without serializing it in the public URL. Self-hosted URLs keep `project` in the public query and do not need `signatureParams`. A custom hostname identifies its project separately, so omit `project` from both the public query and signature. ## Rotating the secret [#rotating-the-secret] **Settings → Security → Signed URLs → Rotate** generates a new secret. Keenpix rejects the old secret as soon as the request reaches the origin. An outer CDN or browser can still serve an immutable response it already cached without revalidating the signature. Rotate if the secret leaks, redeploy URL generation, and purge signed URLs from outer caches. Browser caches may retain already-downloaded responses. Turning signing off keeps the secret stored, so re-enabling does not break existing signed integrations. ## Notes [#notes] * Self-hosted URLs carry `project` publicly. Managed first-party URLs carry it in `/p/` and sign a query copy containing that same id. Custom-domain signatures omit `project` because the verified hostname selects the project before signature checking. * Keenpix verifies the signature before reading its internal transform cache. `sig` is not part of that internal key, so valid requests for the same transform can share a cached variant. * An outer CDN normally keys on the full URL, including `sig`. Never exclude `sig` from an edge cache key unless the edge verifies it before cache lookup. * Signatures have no built-in expiration and do not prevent replay of an existing valid URL. * Signing is not source authentication, image confidentiality, referrer enforcement, or a domain restriction. * SDK prewarm requests are authenticated with an API key and don't need signatures. --- Markdown path: https://keenpix.com/docs/frameworks/angular-family.md # Angular framework family > Native IMAGE_LOADER providers for Angular NgOptimizedImage and Analog. Canonical HTML: [https://keenpix.com/docs/frameworks/angular-family](https://keenpix.com/docs/frameworks/angular-family) Author: Keenpix Team ## Angular [#angular] ```ts import { provideKeenpixImageLoader } from '@keenpix/angular' bootstrapApplication(AppComponent, { providers: [ provideKeenpixImageLoader({ baseUrl: 'https://images.example.com', projectId: 'website', }), ], }) ``` Use Angular's native `NgOptimizedImage` directive normally. Pass Keenpix-specific transform values through `loaderParams` when needed. The adapter maps width, height, quality, format, fit, DPR, and remaining supported transform parameters. ## Analog [#analog] ```ts import { createAnalogImageProvider } from '@keenpix/analog' export const appConfig = { providers: [ createAnalogImageProvider({ baseUrl: 'https://images.example.com', projectId: 'website', }), ], } ``` Analog uses Angular's `IMAGE_LOADER` contract, so template behavior and `NgOptimizedImage` diagnostics remain native. Use `createAnalogImageLoader()` when you need the loader function without provider registration. --- Markdown path: https://keenpix.com/docs/frameworks/astro.md # Astro > Serve remote images through Keenpix with a plain . Canonical HTML: [https://keenpix.com/docs/frameworks/astro](https://keenpix.com/docs/frameworks/astro) Author: Keenpix Team Astro's built-in `` optimizes **local** assets at build time. To optimize **remote** images at request time, offload to Keenpix with the [universal helper](/docs/frameworks#the-universal-pattern) and a native `` (works in `.astro`, MDX, and any UI framework island): ```astro --- import { keenpix, keenpixSrcSet } from '../lib/keenpix' const src = 'https://cdn.example.com/hero.jpg' --- Hero ``` This skips Astro's own image service for that element, letting Keenpix (and your CDN) handle resizing and caching. --- Markdown path: https://keenpix.com/docs/frameworks/catalog.md # Framework package catalog > Complete support matrix for every official @keenpix framework integration. Canonical HTML: [https://keenpix.com/docs/frameworks/catalog](https://keenpix.com/docs/frameworks/catalog) Author: Keenpix Team All packages are ESM, ship TypeScript declarations, are side-effect free where the framework permits it, and are published under MIT. Each package includes its own README and runtime tests. | Package | Integration contract | Support notes | | ------------------------- | -------------------------------------------------- | ----------------------------------------------------- | | `@keenpix/core` | URL builder, loader, srcset, responsive attributes | Foundation for every adapter | | `@keenpix/html` | Escaped attributes and HTML renderer | Any backend or static HTML | | `@keenpix/react` | SSR-safe component and configured factory | React 18/19 | | `@keenpix/next` | Native `next/image` loader and props | Proportional loader dimensions | | `@keenpix/preact` | Preact-compatible image props factory | SSR-safe | | `@keenpix/remix` | React Router/Remix props and loader | Pure and isomorphic | | `@keenpix/tanstack-start` | TanStack Start image props and loader | Pure SSR/hydration output | | `@keenpix/gatsby` | `GatsbyImage` data and URL builder | Dynamic `GatsbyImage`; `StaticImage` must stay static | | `@keenpix/redwood` | RedwoodSDK/RedwoodJS image props | RSC- and prerender-safe | | `@keenpix/waku` | RSC-safe image attributes | Preview while Waku 1.0 is beta | | `@keenpix/docusaurus` | Docusaurus/MDX image props | Does not rewrite local Markdown assets | | `@keenpix/vue` | Vue 3 component and native attributes | SSR and hydration safe | | `@keenpix/nuxt` | Nuxt Image custom provider | Implements Nuxt `getImage` contract | | `@keenpix/vitepress` | VitePress theme extender and Vue component | Preserves the base theme hook | | `@keenpix/svelte` | Lowercase native image attributes | Compiler-independent props API | | `@keenpix/sveltekit` | SvelteKit props and isomorphic loader | No hook required | | `@keenpix/angular` | Native Angular `IMAGE_LOADER` provider | Works with `NgOptimizedImage` | | `@keenpix/analog` | Analog/Angular `IMAGE_LOADER` provider | Uses Angular image contracts | | `@keenpix/solid` | Solid image props | Compiler-independent props API | | `@keenpix/solid-start` | SolidStart props and loader | Isomorphic | | `@keenpix/qwik` | Serializable props and Qwik image transformer | Wrap transformer with Qwik `$()` when required | | `@keenpix/astro` | External image service and HTML attributes | Export at `@keenpix/astro/service` | | `@keenpix/fresh` | Preact component and configured factory | Server-renderable | | `@keenpix/lit` | Lit `TemplateResult` helper | Uses Lit directives for optional attributes | | `@keenpix/ember` | Importable plain helper factories | Modern Ember/strict-mode friendly | | `@keenpix/eleventy` | Universal shortcode, URL filter, and attributes | Extends `@keenpix/html` | | `@keenpix/vite` | `virtual:keenpix` plugin and client types | Works in client and SSR builds | | `@keenpix/expo` | `expo-image` source, sources, and props | Cache policy and responsive web sources | | `@keenpix/react-native` | Native `Image` sources and props | Network source, cache, accessibility, dimensions | ## Version policy [#version-policy] The framework family is a Changesets fixed group. A compatibility change to `@keenpix/core` releases the adapters together, preventing adapters from silently drifting onto incompatible core behavior. Package support follows the peer-dependency ranges in each package manifest. Preview status means the upstream framework itself is still changing, not that the package skips validation. All 29 packages inherit automatic Client Hint transforms from `@keenpix/core`. Signing stays server-only through `@keenpix/sdk/signing`, and project watermark policy is applied at delivery time, so adapters do not duplicate security or overlay logic. --- Markdown path: https://keenpix.com/docs/frameworks/html.md # Plain HTML & any backend > No framework required — build the URL anywhere. Canonical HTML: [https://keenpix.com/docs/frameworks/html](https://keenpix.com/docs/frameworks/html) Author: Keenpix Team Because Keenpix is just a URL, it works with no framework at all — and with any server-side language (PHP, Ruby, Python, Go, …). Build the URL and render an ``. ## Static HTML [#static-html] ```html Hero ``` With no `fmt` parameter, Keenpix negotiates the output from the browser's `Accept` header. Add `fmt=avif`, `fmt=webp`, `fmt=jpeg`, `fmt=png`, or `fmt=svg` only when you want to force one format. `fmt=svg` is explicit SVG delivery. If the source is SVG and `fmt` is omitted or set to `auto`, Keenpix returns a negotiated raster format instead. ## Responsive with `srcset` [#responsive-with-srcset] ```html Hero ``` ## Server-side (example: PHP) [#server-side-example-php] ```php 'YOUR_ID', 'w' => $w, 'fmt' => 'auto']); return "https://keenpix.example.com/img/" . rawurlencode($src) . "?$q"; } ?> Hero ``` The same one-liner works in Rails, Django, Laravel, or a CMS template — just URL-encode source URLs that contain their own query string or fragment. --- Markdown path: https://keenpix.com/docs/frameworks.md # Framework integrations > Official @keenpix packages for web frameworks, meta-frameworks, static generators, and native apps. Canonical HTML: [https://keenpix.com/docs/frameworks](https://keenpix.com/docs/frameworks) Author: Keenpix Team Last reviewed: 2026-08-16 Keenpix image delivery works with plain URLs, but the official MIT-licensed package family gives each framework its native integration shape. All adapters extend `@keenpix/core`, so URL construction, source normalization, proportional responsive dimensions, signing payloads, and transform parameters stay consistent. ```bash pnpm add @keenpix/react # or npm install @keenpix/react ``` Every adapter takes the same public delivery configuration: ```ts import { createManagedKeenpixConfig } from '@keenpix/core' const config = createManagedKeenpixConfig('website') ``` `baseUrl` and `projectId` are safe to expose in rendered HTML. Management API keys and signing secrets are server-only. ## Core primitives [#core-primitives] Use `@keenpix/core` when a framework has no dedicated adapter or when you are building an integration library: ```ts import { createKeenpix, createImageAttributes } from '@keenpix/core' const keenpix = createKeenpix(config) const src = keenpix.url('/hero.jpg', { width: 1280, format: 'webp' }) const hinted = keenpix.url('/hero.jpg', { width: 'auto', dpr: 'auto' }) const attributes = createImageAttributes(config, { alt: 'Hero', src: '/hero.jpg', widths: [640, 960, 1280], }) ``` Transform URLs do not require the management SDK or an API key. Use [`@keenpix/sdk`](/docs/reference/sdk-package) only from trusted server code when automating projects, domains, configuration, or prewarming. Every framework adapter inherits `width: 'auto'` and `dpr: 'auto'` from `@keenpix/core`. The browser-facing packages never receive a signing secret; generate HMAC URLs in trusted server/build code with `@keenpix/sdk/signing` and pass the resulting URL to the framework integration. Project watermarks require no adapter option because the transform service applies them consistently. --- Markdown path: https://keenpix.com/docs/frameworks/native.md # Expo and React Native > Keenpix network sources, cache policy, responsive alternatives, and accessibility props for native applications. Canonical HTML: [https://keenpix.com/docs/frameworks/native](https://keenpix.com/docs/frameworks/native) Author: Keenpix Team ## Expo Image [#expo-image] ```tsx import { Image } from 'expo-image' import { createExpoImageProps } from '@keenpix/expo' const props = createExpoImageProps(config, { alt: 'Hero', height: 720, src: '/hero.jpg', width: 1280, }) export function Hero() { return } ``` Use `createExpoImageSource()` for one source or `createExpoImageSources()` for dimensioned alternatives and responsive web sources. The adapter supports Expo Image cache policy and content-fit conventions. ## React Native Image [#react-native-image] ```tsx import { Image } from 'react-native' import { createReactNativeImageProps } from '@keenpix/react-native' export function Hero() { return } ``` Use `createReactNativeImageSource()` and `createReactNativeImageSources()` when composing source arrays yourself. The result uses native network source objects, cache settings, accessibility labels, and explicit dimensions rather than browser-only `srcset`. Do not place management API keys or transform signing secrets in a mobile bundle. Public `baseUrl` and `projectId` are sufficient for normal image delivery. --- Markdown path: https://keenpix.com/docs/frameworks/nextjs.md # Next.js > Configure next/image with a Keenpix custom loader, responsive sizes, managed or self-hosted delivery, origin controls, and production validation. Canonical HTML: [https://keenpix.com/docs/frameworks/nextjs](https://keenpix.com/docs/frameworks/nextjs) Author: Keenpix Team Last reviewed: 2026-08-18 `next/image` lets you swap its optimizer for a custom loader. Point it at Keenpix and keep using `` exactly as before — Next still generates the `srcset`, Keenpix serves each width. **Use this setup when you want image transformation and delivery to remain separate from the Next.js server or hosting platform.** Keep the built-in optimizer when it already meets your deployment, cache, and cost requirements. A custom image CDN adds another service boundary, so the gain must justify the URL migration, cache policy, and failure mode. ## 1. Add the loader [#1-add-the-loader] ```js // keenpix-loader.js export default function keenpixLoader({ src, width, quality }) { const baseUrl = process.env.NEXT_PUBLIC_KEENPIX_BASE_URL const project = process.env.NEXT_PUBLIC_KEENPIX_PROJECT const origin = process.env.NEXT_PUBLIC_IMAGE_ORIGIN ?? '' // next/image passes `src` as-is; Keenpix needs an absolute URL. const url = src.startsWith('http') ? src : `${origin}${src}` const params = new URLSearchParams({ w: String(width), fmt: 'auto' }) if (project) params.set('project', project) if (quality) params.set('q', String(quality)) return `${baseUrl}/img/${encodeURIComponent(url)}?${params.toString()}` } ``` Use the canonical managed-cloud path without a project query: ```dotenv NEXT_PUBLIC_KEENPIX_BASE_URL=https://cdn.keenpix.com/p/YOUR_PROJECT_ID NEXT_PUBLIC_IMAGE_ORIGIN=https://assets.example.com ``` For a self-hosted instance, set `NEXT_PUBLIC_KEENPIX_BASE_URL` to your image host and set `NEXT_PUBLIC_KEENPIX_PROJECT`; the loader adds that project id to the query. ## 2. Point next.config at it [#2-point-nextconfig-at-it] ```js // next.config.js module.exports = { images: { loader: 'custom', loaderFile: './keenpix-loader.js' }, } ``` ## 3. Use `` normally [#3-use-image-normally] ```jsx import Image from 'next/image' export default function Hero() { return Spring } ``` Add the image origin host to the project's allowlist (**Settings → Security**). Set `NEXT_PUBLIC_KEENPIX_BASE_URL` and `NEXT_PUBLIC_IMAGE_ORIGIN` in `.env`, plus `NEXT_PUBLIC_KEENPIX_PROJECT` on self-hosted deployments. ## Set `sizes` for responsive layouts [#set-sizes-for-responsive-layouts] The loader receives each width that Next.js puts into the generated `srcset`. The browser still needs an accurate `sizes` value to choose among those candidates. Without it, a responsive image can be treated as viewport-wide and download more pixels than its actual slot needs. ```jsx Canvas backpack in olive green ``` Keep width candidates aligned with real component breakpoints. More variants are not automatically better: they create more cache keys, and adjacent widths that produce almost the same byte size add little value. ## Static export and non-Vercel deployments [#static-export-and-non-vercel-deployments] A custom loader returns ordinary external image URLs, so it also works when Next.js emits a static export and no Next.js image-optimization server exists at runtime. Keenpix still has to be reachable from the browser, and every relative `src` still needs an absolute `NEXT_PUBLIC_IMAGE_ORIGIN`. For managed delivery, the public project id belongs in the `cdn.keenpix.com/p/` path. It is routing context, not a secret. For self-hosting, the project query parameter selects the project on your own image host. The transform URL concepts are similar, but moving between the two paths still requires hostname, cache, and configuration planning. ## Protect the source and URL grammar [#protect-the-source-and-url-grammar] Keenpix only fetches from hosts in the project's origin allowlist. Start with the narrowest set of source hosts your application actually uses and verify that an unapproved host fails. The fetcher also blocks private/internal addresses and revalidates redirects, but the allowlist remains the first product-level boundary. Do not put an HMAC signing secret in `keenpix-loader.js`; the loader is browser-facing. If your project requires signed transform URLs, sign them on a server you control or route the request through a server-side signing endpoint. Signatures are useful when third parties could append parameters and create expensive cache misses. They do not stop someone from copying and replaying an already valid public image URL. ## Validate the integration before launch [#validate-the-integration-before-launch] Check the result as a delivery pipeline, not only as a rendered image: 1. Inspect the final `` and confirm `srcset` contains several Keenpix URLs with different `w` values. 2. Resize the viewport and verify the browser selects a candidate appropriate to the rendered slot. 3. Check `Content-Type`, `Vary`, and `Cache-Control` on AVIF, WebP, and fallback responses. 4. Confirm an unapproved source host is rejected. 5. Compare decoded output and transferred bytes with your own source files and settings. 6. Test an uncached request, a repeat request, an origin failure, and the rollback path. `fmt=auto` varies the response by the browser's `Accept` header. Every cache in front of the image service must keep those variants separate. If the outer CDN cannot vary safely, use explicit AVIF/WebP URLs and provide a fallback through `` instead. ## Decide on cost and ownership [#decide-on-cost-and-ownership] Next.js and Vercel's built-in image optimization can be the simplest option because there is no new provider or URL migration. Keenpix is a better candidate when the team wants one image URL grammar across frameworks, a separately measured delivery layer, published managed-delivery allowances, or a future self-host path. Neither architecture is universally cheaper or faster; model requests, cache reads/writes, delivered bytes, origin behavior, and engineering time from your own workload. * Review the [managed image CDN pricing](/pricing). * Compare [Keenpix with Vercel Image Optimization](/compare/vercel-image-optimization-alternative). * Read the deeper [Next.js custom-loader production guide](/blog/nextjs-custom-image-cdn-loader). * If you own the operations stack, review [self-hosted image CDN deployment](/self-hosted-image-cdn). --- Markdown path: https://keenpix.com/docs/frameworks/nuxt.md # Nuxt > Register Keenpix as a custom @nuxt/image provider. Canonical HTML: [https://keenpix.com/docs/frameworks/nuxt](https://keenpix.com/docs/frameworks/nuxt) Author: Keenpix Team Last reviewed: 2026-08-16 [`@nuxt/image`](https://image.nuxt.com) supports custom providers, so `` and `` can route through Keenpix with full responsive support. ## 1. Add a provider [#1-add-a-provider] ```ts // providers/keenpix.ts import { createOperationsGenerator } from '#image' const operationsGenerator = createOperationsGenerator({ keyMap: { width: 'w', height: 'h', quality: 'q', format: 'fmt', fit: 'fit' }, joinWith: '&', formatter: (key, value) => `${key}=${value}`, }) export const getImage = (src, { modifiers = {}, baseURL = '', project = '' } = {}) => { const operations = operationsGenerator(modifiers) const url = src.startsWith('http') ? src : `${baseURL}${src}` // Keenpix needs an absolute URL return { url: `https://cdn.keenpix.com/p/${encodeURIComponent(project)}/img/${encodeURIComponent(url)}${operations ? `?${operations}` : ''}`, } } ``` ## 2. Register it [#2-register-it] ```ts // nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxt/image'], image: { providers: { keenpix: { name: 'keenpix', provider: '~/providers/keenpix.ts', options: { baseURL: 'https://cdn.example.com', project: 'YOUR_ID' }, }, }, }, }) ``` ## 3. Use it [#3-use-it] ```vue ``` Leaving `format` unset means the generated Keenpix URL has no `fmt` parameter, so Keenpix uses the browser's `Accept` header and serves AVIF, WebP, or JPEG based on support, including for SVG sources. Passing `format="avif"` or `format="webp"` intentionally forces that output format; passing `format="svg"` is required when you want an SVG origin returned as optimized SVG. In `@nuxt/image`, the `format` prop maps to Keenpix's `fmt` query parameter. Do not pass a helper that always returns `avif` unless you want to force AVIF for every client. For JoodCMS-style wrappers, keep the wrapper's default `format` value undefined and only pass a format for deliberate fixed-format URLs. The snippet above is for managed cloud and never emits the retired application hostname. For self-hosting, use `@keenpix/nuxt` with your own delivery base URL and public project query. If you don't need `@nuxt/image`'s responsive machinery, skip the provider and bind a plain `` using the [universal helper](/docs/frameworks#the-universal-pattern). --- Markdown path: https://keenpix.com/docs/frameworks/react-family.md # React framework family > React, Next.js, Preact, Remix, TanStack Start, Gatsby, Redwood, Waku, and Docusaurus integrations. Canonical HTML: [https://keenpix.com/docs/frameworks/react-family](https://keenpix.com/docs/frameworks/react-family) Author: Keenpix Team ## React [#react] ```tsx import { createKeenpixImage } from '@keenpix/react' const KeenpixImage = createKeenpixImage({ baseUrl: 'https://images.example.com', projectId: 'website', }) export function Hero() { return } ``` The configured component is SSR-safe and does not depend on browser globals. ## Next.js [#nextjs] ```tsx import Image from 'next/image' import { createNextLoader } from '@keenpix/next' const loader = createNextLoader({ baseUrl: 'https://images.example.com', projectId: 'website', }) export function Hero() { return Hero } ``` Use `createNextImageProps()` when composing props separately. Next's loader receives display dimensions, so responsive candidate heights remain proportional. ## Props-first React frameworks [#props-first-react-frameworks] Preact, Remix/React Router, TanStack Start, Redwood, Waku, and Docusaurus expose pure factories with the same call shape: ```tsx import { createTanStackImage } from '@keenpix/tanstack-start' const image = createTanStackImage({ baseUrl: 'https://images.example.com', projectId: 'website', }) export function Hero() { return } ``` Swap the import and factory for your runtime: | Package | Factory | | ------------------------- | ---------------------------------------------------- | | `@keenpix/preact` | `createPreactImage` | | `@keenpix/remix` | `createRemixImage` or `createRemixImageLoader` | | `@keenpix/tanstack-start` | `createTanStackImage` or `createTanStackImageLoader` | | `@keenpix/redwood` | `createRedwoodImage` | | `@keenpix/waku` | `createWakuImage` | | `@keenpix/docusaurus` | `createDocusaurusImage` | Waku support is preview while Waku 1.0 is beta. Docusaurus keeps local Markdown assets on Docusaurus' native pipeline; use the adapter for remote/dynamic images and MDX components. ## Gatsby [#gatsby] ```tsx import { GatsbyImage } from 'gatsby-plugin-image' import { createGatsbyImageData } from '@keenpix/gatsby' const image = createGatsbyImageData(config, { alt: 'Hero', height: 720, src: '/hero.jpg', width: 1280, }) export function Hero() { return } ``` Gatsby's `StaticImage` requires statically analyzable source data and cannot use a runtime loader. Use `GatsbyImage` for Keenpix delivery. --- Markdown path: https://keenpix.com/docs/frameworks/react.md # React (Vite) > A small component for any React app. Canonical HTML: [https://keenpix.com/docs/frameworks/react](https://keenpix.com/docs/frameworks/react) Author: Keenpix Team For any React app (Vite, CRA, Parcel…), wrap the [universal helper](/docs/frameworks#the-universal-pattern) in a reusable component: ```tsx // KeenpixImage.tsx import { keenpix, keenpixSrcSet } from './keenpix' type Props = React.ComponentProps<'img'> & { src: string; width?: number } export function KeenpixImage({ src, width = 1280, sizes = '100vw', ...rest }: Props) { return ( ) } ``` ```tsx ``` This works identically in Solid, Qwik, and Preact — only the `class`/`className` prop name differs. --- Markdown path: https://keenpix.com/docs/frameworks/remix.md # Remix / React Router > Optimize responsive images in Remix and React Router with Keenpix URL helpers and native image markup. Canonical HTML: [https://keenpix.com/docs/frameworks/remix](https://keenpix.com/docs/frameworks/remix) Author: Keenpix Team Remix (and React Router v7) has no image component, so use the [universal helper](/docs/frameworks#the-universal-pattern) with a native `` — or the same `` from the [React guide](/docs/frameworks/react). ```tsx import { keenpix, keenpixSrcSet } from '~/lib/keenpix' export default function Hero() { const src = 'https://cdn.example.com/hero.jpg' return ( Hero ) } ``` Expose the host/project to the client via Remix's `window.ENV` pattern (a `loader` + a ` ``` ## SvelteKit [#sveltekit] ```ts // src/lib/keenpix.ts import { createSvelteKitKeenpix } from '@keenpix/sveltekit' export const keenpix = createSvelteKitKeenpix({ baseUrl: 'https://images.example.com', projectId: 'website', }) ``` Use the same `keenpix.imageProps()` call inside routes and components. `keenpix.loader()` is available when a library expects a URL loader. The adapters are compiler-independent and pure: there is no Svelte preprocessor or SvelteKit hook to install. Output is stable for SSR, prerendering, hydration, and client navigation. --- Markdown path: https://keenpix.com/docs/frameworks/sveltekit.md # SvelteKit > Build responsive Keenpix image URLs and srcsets directly in SvelteKit components. Canonical HTML: [https://keenpix.com/docs/frameworks/sveltekit](https://keenpix.com/docs/frameworks/sveltekit) Author: Keenpix Team Use the [universal helper](/docs/frameworks#the-universal-pattern) directly in markup: ```svelte ``` ```svelte ``` Put `PUBLIC_KEENPIX_HOST` / `PUBLIC_KEENPIX_PROJECT` in `.env` and read them via `$env/static/public` inside `keenpix.ts`. --- Markdown path: https://keenpix.com/docs/frameworks/tanstack-start.md # TanStack Start > Add responsive Keenpix image delivery to TanStack Start and TanStack Router applications. Canonical HTML: [https://keenpix.com/docs/frameworks/tanstack-start](https://keenpix.com/docs/frameworks/tanstack-start) Author: Keenpix Team TanStack Start (and TanStack Router) has no built-in image component, so use the [universal helper](/docs/frameworks#the-universal-pattern) with a native ``. This is also exactly how the Keenpix dashboard itself is built. ```tsx import { keenpix, keenpixSrcSet } from '~/lib/keenpix' export function Hero() { return ( Hero ) } ``` Read `HOST` / `PROJECT` from `import.meta.env` (e.g. `VITE_KEENPIX_HOST`) so the helper works in both server and client renders. --- Markdown path: https://keenpix.com/docs/frameworks/vue-family.md # Vue framework family > Vue 3, Nuxt Image, and VitePress integrations. Canonical HTML: [https://keenpix.com/docs/frameworks/vue-family](https://keenpix.com/docs/frameworks/vue-family) Author: Keenpix Team ## Vue 3 [#vue-3] ```vue ``` Use `imageProps()` with `v-bind` when you want a native ``. The adapter returns lowercase `srcset`, matching Vue DOM bindings. ## Nuxt Image [#nuxt-image] ```ts // providers/keenpix.ts import { createNuxtImageProvider } from '@keenpix/nuxt' export const getImage = createNuxtImageProvider({ baseUrl: 'https://images.example.com', projectId: 'website', }) ``` Register it as a custom provider in `nuxt.config.ts`, then use that provider with ``. The adapter implements Nuxt Image's `getImage` response contract and maps provider modifiers into Keenpix transforms. ## VitePress [#vitepress] ```ts // .vitepress/theme/index.ts import DefaultTheme from 'vitepress/theme' import { createVueKeenpix, withKeenpixTheme } from '@keenpix/vitepress' const config = { baseUrl: 'https://images.example.com', projectId: 'docs' } const { Image } = createVueKeenpix(config) export default withKeenpixTheme(DefaultTheme, config, Image) ``` The component is registered as ``. Markdown image syntax is not rewritten automatically, preserving VitePress' asset pipeline. --- Markdown path: https://keenpix.com/docs/frameworks/vue.md # Vue > Bind :src to the URL helper. Canonical HTML: [https://keenpix.com/docs/frameworks/vue](https://keenpix.com/docs/frameworks/vue) Author: Keenpix Team In any Vue 3 app, import the [universal helper](/docs/frameworks#the-universal-pattern) and bind it. For reuse, make a tiny component: ```vue ``` ```vue ``` (Prefer `@nuxt/image`? See the [Nuxt guide](/docs/frameworks/nuxt) for a custom provider.) --- Markdown path: https://keenpix.com/docs/frameworks/web-and-meta-frameworks.md # Web and meta-frameworks > Astro, Qwik, Solid, Fresh, Lit, Ember, Eleventy, Vite, and plain HTML integrations. Canonical HTML: [https://keenpix.com/docs/frameworks/web-and-meta-frameworks](https://keenpix.com/docs/frameworks/web-and-meta-frameworks) Author: Keenpix Team ## Astro [#astro] Configure the native external image service with `@keenpix/astro/service`, or call `createAstroImageService()` and `createAstroImageAttributes()` directly. Numeric quality is forwarded; named Astro quality presets are not remapped. ```ts // astro.config.ts import { defineConfig } from 'astro/config' export default defineConfig({ image: { service: { entrypoint: '@keenpix/astro/service', config: { baseUrl: 'https://images.example.com', projectId: 'website', }, }, }, }) ``` ## Qwik [#qwik] `createQwikKeenpix()` returns serializable image props and a transformer compatible with Qwik's image tooling. Wrap the transformer with Qwik `$()` at the application boundary when the consuming API requires a QRL. ## Solid and SolidStart [#solid-and-solidstart] Use `createSolidKeenpix()` or `createSolidStartKeenpix()` and spread `imageProps()` onto a native ``. SolidStart also exports `createSolidStartImageLoader`. Both APIs are pure and SSR-safe. ## Fresh [#fresh] `@keenpix/fresh` exports a server-renderable `KeenpixImage`, `createFreshImageProps()`, and `createKeenpixImage()` configured factory. It uses Preact-compatible output without requiring client hydration. ## Lit [#lit] Use `keenpixImage(config, input)` for a Lit `TemplateResult`, or `createLitImageAttributes()` when composing your own template. Optional attributes use Lit's `ifDefined` semantics. ## Ember [#ember] `createKeenpixUrlHelper()` and `createKeenpixImageAttributesHelper()` return importable plain helper functions suitable for modern Ember strict-mode templates. The package does not require legacy global helper registration. ## Eleventy [#eleventy] Register `keenpixEleventyPlugin()` to get a universal image shortcode and URL filter. `createEleventyImageAttributes()` is also available for custom templates. HTML escaping and rendering come from `@keenpix/html`. ## Vite [#vite] ```ts // vite.config.ts import { keenpix } from '@keenpix/vite' export default { plugins: [keenpix(config)] } ``` Import the configured helper from `virtual:keenpix`. Add `/// ` to `vite-env.d.ts` for virtual-module types. ## Plain HTML [#plain-html] `createHtmlImageAttributes()` returns safe attributes; `renderKeenpixImage()` returns an escaped responsive `` string. Use `@keenpix/core` directly for URL-only integrations in any language or backend. --- Markdown path: https://keenpix.com/docs/getting-started/cloud-quickstart.md # Cloud quickstart > From signup to your first optimized image on managed cloud — no server required. Canonical HTML: [https://keenpix.com/docs/getting-started/cloud-quickstart](https://keenpix.com/docs/getting-started/cloud-quickstart) Author: Keenpix Team Last reviewed: 2026-08-15 Prefer not to run your own instance? The managed cloud at [keenpix.com](https://keenpix.com) is the same engine, hosted — this page gets you from signup to an optimized image in a few minutes. Self-hosting instead? See the [Quickstart](/docs/getting-started/quickstart). ## 1. Sign up and start your trial [#1-sign-up-and-start-your-trial] Create an account at [keenpix.com](https://keenpix.com). Every plan starts with a **14-day free trial** — a card is required, but trial usage is never billed. During the trial you get the plan's full features with up to **2 projects** and **20 GB delivered**. ## 2. Create a project [#2-create-a-project] From the dashboard, create a project and enter the hostname your images live on (e.g. `cdn.example.com` or your S3/R2 bucket host). That origin hostname is **automatically added to the project's allowlist**, so your first transform works right away — no API key, no extra security step. It's under **Settings → General → Project ID**. Managed delivery puts it in the canonical path: `https://cdn.keenpix.com/p/YOUR_ID`. ## 3. Copy the transform URL [#3-copy-the-transform-url] A request is just a URL — prefix your image with the cloud endpoint: ```html Hero ``` Encode the complete source URL with `encodeURIComponent(source)` before appending it after `/img/`. This preserves `https://`, query parameters, and fragments as one source value. Managed delivery is available only through the canonical CDN hostname. Use `fmt=auto` to negotiate AVIF/WebP/JPEG from each browser's `Accept` header instead of pinning a format. The first request is a cache **MISS** (Keenpix fetches + transforms the original); every later request for the same parameters is a **HIT**. ## 4. Point your site at it [#4-point-your-site-at-it] Swap your image URLs — or your framework's image loader — to the transform endpoint. Most codebases build image URLs in one place, so this is typically a single-file change. See the [framework guides](/docs/frameworks) for Next.js, Nuxt, TanStack Start, SvelteKit, Astro, and more. ## 5. Watch the savings [#5-watch-the-savings] Open **Analytics** to see bandwidth saved, cache hit rate, format mix, and live request logs — included on every plan. Billing counts optimized bytes delivered through the managed Keenpix network. A Cloudflare edge hit, a Keenpix cache hit, and a newly optimized response each count once; transformations and team members are unlimited. Browser or customer-owned CDN cache hits that never reach Keenpix are not counted. Bandwidth saved is a separate analytics comparison and is never added to billable delivered bytes. See the [API reference](/docs/reference/parameters) for the full parameter list, or [Projects & access](/docs/getting-started/projects) for how allowlists gate requests. --- Markdown path: https://keenpix.com/docs/getting-started/projects.md # Projects & access > How projects, the allowlist, and keyless transform URLs fit together. Canonical HTML: [https://keenpix.com/docs/getting-started/projects](https://keenpix.com/docs/getting-started/projects) Author: Keenpix Team A **project** is Keenpix's core primitive: it's one origin, one allowlist, and one analytics view. A single Keenpix instance can host many projects (one per site, brand, or environment). ## Keyless transforms by design [#keyless-transforms-by-design] The transform endpoint needs no API key. Access is gated entirely by each project's **domain allowlist** — Keenpix will only fetch an image whose source host is on the list. This means: * You can put the transform URL directly in public HTML; there's no secret to leak. * An empty allowlist blocks **every** request (fail-closed). * Private/internal addresses are always refused (see [Security](/docs/concepts/security)). For trusted backend systems that manage projects programmatically, Keenpix also offers **internal API keys** over the JSON API. Those are separate from the public transform endpoint, which stays keyless. See the [SDK API reference](/docs/reference/sdk-api) for project management, allowed-host updates, configuration discovery, and cache prewarming. ## Anatomy of a request [#anatomy-of-a-request] ```text https://keenpix.example.com/img/https://cdn.example.com/hero.jpg ?project=PROJECT_ID ← which project (its allowlist gates access) &w=1200&fmt=auto ← transforms ``` ## Scoping the dashboard [#scoping-the-dashboard] The dashboard works at two levels: **All projects** (org-wide analytics and logs) or a single project picked from the switcher at the top of the sidebar. Settings are always per-project. --- Markdown path: https://keenpix.com/docs/getting-started/quickstart.md # Quickstart > Your first optimized image in two minutes — no SDK, no API key. Canonical HTML: [https://keenpix.com/docs/getting-started/quickstart](https://keenpix.com/docs/getting-started/quickstart) Author: Keenpix Team Keenpix is just an HTTP endpoint, so the fastest integration is a plain `` tag. ## 1. Allow your source host [#1-allow-your-source-host] Open **Settings → Security → Allowed hosts** for your project and add the hostname your images come from (e.g. `cdn.example.com`). Keenpix refuses to fetch from any host that isn't on the list — an empty allowlist blocks every request. This is the entire access model for transform URLs: no API key required. It's under **Settings → General → Project ID**. You pass it as `project=` in every URL. ## 2. Point an `` at the endpoint [#2-point-an-img-at-the-endpoint] ```html Hero ``` The first request is a cache **MISS** (Keenpix fetches + transforms the original); every later request for the same parameters is a **HIT** served straight from disk. ## 3. Tune the output [#3-tune-the-output] Add query parameters to resize and re-encode: | Param | Example | Effect | | ----- | ---------- | ----------------------------------------------------------------------------------------- | | `w` | `w=1200` | Resize to 1200px wide | | `q` | `q=80` | Quality 30–100 (default from settings) | | `fmt` | `fmt=auto` | Negotiate AVIF/WebP/JPEG from `Accept`; use `fmt=svg` explicitly for optimized SVG output | | `dpr` | `dpr=2` | Render at 2× for retina screens | See the [API reference](/docs/reference/parameters) for the full list, or jump to your [framework guide](/docs/frameworks). --- Markdown path: https://keenpix.com/docs.md # Documentation > Drop-in image optimization — managed cloud or self-hosted, with optional framework packages and a server SDK. Canonical HTML: [https://keenpix.com/docs](https://keenpix.com/docs) Author: Keenpix Team Keenpix is an image-optimization service you can use two ways — [managed cloud](https://keenpix.com) at keenpix.com or [self-hosted](/docs/self-hosting) on your own infrastructure — and an open-source alternative to [ImageKit](/blog/keenpix-vs-imagekit), [imgix](/blog/keenpix-vs-imgix), and [Cloudinary](/blog/keenpix-vs-cloudinary). Its public transform API is a single HTTP endpoint that fetches an image from your origin, transforms it with [sharp](https://sharp.pixelplumbing.com) (resize, crop, filter, and re-encode), optimizes SVG explicitly with `fmt=svg`, caches the result on disk, and serves it behind a long-lived, immutable `Cache-Control`. Image delivery needs no runtime SDK and no API key in your image URLs — **a request is just a URL**. Optional `@keenpix/*` framework packages provide native loaders and components, while `@keenpix/sdk` automates trusted server-side management: ```html Hero ``` ## Start here [#start-here] * [**Quickstart**](/docs/getting-started/quickstart) — your first optimized image in two minutes. * [**Cloud quickstart**](/docs/getting-started/cloud-quickstart) — the managed option: signup to optimized image, no server required. * [**How it works**](/docs/concepts/how-it-works) — the request lifecycle, caching, and security model. * [**Frameworks**](/docs/frameworks) — drop Keenpix into Next.js, Nuxt, TanStack Start, SvelteKit, Astro, and more. * [**API reference**](/docs/reference/endpoint) — transform parameters, response headers, and the authenticated SDK API. * [**Self-hosting**](/docs/self-hosting) — deploy with Docker in about a minute. ## Why Keenpix [#why-keenpix] * **Drop-in.** Any framework that renders an `` works — no rewrite required. * **Cloud or self-hosted.** Use the managed cloud at keenpix.com, or run the app, transform data plane, worker, and docs images on your own infrastructure; put a configured CDN in front of `/img/*`. * **Keyless.** Access is gated by a per-project domain allowlist, not by secrets. * **Built-in analytics.** Every origin request is logged, and optional Cloudflare edge analytics show the cache layer in front. --- Markdown path: https://keenpix.com/docs/reference/endpoint.md # Transform endpoint > GET /img/ - the public image transform endpoint. Canonical HTML: [https://keenpix.com/docs/reference/endpoint](https://keenpix.com/docs/reference/endpoint) Author: Keenpix Team Keenpix exposes one public image endpoint. No API key is used for image URLs - the project's allowlist is the access control. ```http GET /img/SOURCE_URL?project=PROJECT_ID&w=…&fmt=… GET /img/?url=SOURCE_URL&project=PROJECT_ID&w=…&fmt=… ``` | Part | Required | Notes | | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | path source or `url` | yes | Absolute source URL after `/img/`, or in the `url` query parameter when no path source is present. Host must be allowlisted. Missing -> `400`. | | `project` | yes | Project id; its allowlist gates access. Missing → `400`. | | transforms | no | `w`, `h`, `q`, `fmt`, `fit`, `dpr`, `blur`, Sharp/IPX-style modifiers — see [Parameters](/docs/reference/parameters). | Simple source URLs can be written directly in the path. If the source URL contains its own `?` or `#`, URL-encode the source before appending Keenpix transform parameters. The `url` query parameter is also accepted for backends or integrations that prefer to keep the source URL out of the path. ## Example [#example] ```bash curl -I "https://keenpix.example.com/img/https://cdn.example.com/hero.jpg?project=store&w=800&fmt=webp" # HTTP/1.1 200 OK # content-type: image/webp # cache-control: public, max-age=31536000, immutable # accept-ch: Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width # vary: Accept, Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width, DPR, Width, Viewport-Width curl -I "https://keenpix.example.com/img/?url=https%3A%2F%2Fcdn.example.com%2Fhero.jpg&project=store&w=800&fmt=webp" ``` Next steps: [Parameters](/docs/reference/parameters) · [Responses & errors](/docs/reference/responses) · [SDK API](/docs/reference/sdk-api). --- Markdown path: https://keenpix.com/docs/reference/operations.md # Operations > Runtime endpoints for deployment health and automation. Canonical HTML: [https://keenpix.com/docs/reference/operations](https://keenpix.com/docs/reference/operations) Author: Keenpix Team These endpoints are for operators and deployment platforms. They are separate from the public image transform endpoint and the authenticated SDK API. ## Health [#health] ```http GET /api/health ``` The health endpoint is unauthenticated so container platforms, reverse proxies, and uptime monitors can call it. It returns JSON with `Cache-Control: no-store`. | Status | Meaning | | ------ | ------------------------------------------------------------- | | `200` | The app can reach the database and runtime checks completed. | | `503` | The database health check failed or the instance is degraded. | Example response: ```json { "ok": true, "service": "keenpix", "status": "ok", "timestamp": "2026-06-12T02:30:00.000Z", "uptimeSeconds": 123, "checks": { "cache": { "diskSizeBytes": 1048576, "diskMaxBytes": 2147483648, "memorySizeBytes": 262144, "memoryMaxBytes": 67108864 }, "database": { "ok": true, "latencyMs": 2 }, "prewarmQueue": { "active": 0, "waiting": 0, "delayed": 0, "queued": 0, "failed": 0, "status": "ready" } }, "latencyMs": 3 } ``` The exact cache and prewarm queue fields may grow as the operations dashboard grows, so treat `ok`, `status`, `checks.database.ok`, and the HTTP status as the stable monitoring contract. ## Worker operations [#worker-operations] The worker has a separate internal ops server on `KEENPIX_WORKER_PORT` (default `3001`). Compose keeps this port private and uses the cheap liveness endpoint for container health: | Endpoint | Purpose | | --------------------- | ---------------------------------------------------------------------- | | `GET /health/live` | Confirms the worker process and HTTP server are alive. | | `GET /health/ready` | Confirms the BullMQ consumer is accepting jobs and Dragonfly responds. | | `GET /health` | Compatibility alias for readiness. | | `GET /health/details` | Returns component status, queue latency, environment, and uptime. | | `GET /workbench` | Opens the embedded BullMQ queue dashboard. | Set both `KEENPIX_WORKBENCH_USERNAME` and `KEENPIX_WORKBENCH_PASSWORD` before assigning the worker service a private admin domain. Never expose port `3001` publicly without authentication; an SSH tunnel or access-controlled internal domain is preferred. --- Markdown path: https://keenpix.com/docs/reference/parameters.md # Parameters > Every query parameter the transform endpoint accepts. Canonical HTML: [https://keenpix.com/docs/reference/parameters](https://keenpix.com/docs/reference/parameters) Author: Keenpix Team All transform parameters are optional; out-of-range values are clamped, not rejected. | Param | Type | Range / values | Default | Description | | ------------------------------ | --------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | `project` | string | — | **required** | Project id (allowlist gates access) | | `sig` | string | base64url HMAC-SHA256 | required when the project enables [signed URLs](/docs/concepts/signed-urls) | Request signature over the source URL + sorted params | | `kid` | int | current project key version | required for signed URLs | Invalidates old URL generations after secret rotation | | `iat` / `exp` | Unix seconds | project signing policy | required when a signing lifetime is configured | Signed issuance and expiration timestamps | | `url` | URL | absolute `http`/`https` URL | — | Source image URL when not using the `/img/` path form | | `w` / `h` | int or `auto` for `w` | 1–5000 | bounded original | Target width/height; `w=auto` resolves `Sec-CH-Width`, then `Sec-CH-Viewport-Width` | | `resize` / `s` | string | `WIDTHxHEIGHT`, `WIDTH`, `xHEIGHT` | — | Compact size alias; explicit `w`/`h` win | | `q` | int | 30–100 | project default | Output quality | | `fmt` | enum | `auto` `avif` `webp` `jpeg` `png` `gif` `heif` `tiff` `svg` | `auto` | Output format (`auto` negotiates AVIF/WebP/JPEG) | | `fit` | enum | `cover` `contain` `fill` `inside` `outside` | `cover` | How to fit the `w`×`h` box | | `position` / `pos` / `gravity` | enum | edges, corners, compass gravities, `entropy`, `attention` | `centre` | Crop anchor for `cover`/`contain` | | `dpr` | number or `auto` | 1–3 | `1` | Device pixel ratio; `auto` resolves the `Sec-CH-DPR` Client Hint | | `enlarge` | bool | `1` `true` `yes` `on` | `false` | Allow upscaling past source dimensions | | `kernel` | enum | `nearest` `linear` `cubic` `mitchell` `lanczos2` `lanczos3` `mks2013` `mks2021` | `lanczos3` | Resize kernel | | `background` / `bg` | color | Sharp color string | — | Fill color for `contain`, `flatten`, `extend`, and arbitrary rotate | | `flatten` | bool | `1` `true` `yes` `on` | `false` | Merge alpha onto `background` | | `extract` / `crop` | string | `left,top,width,height` | — | Crop an exact source rectangle before resize | | `trim` | bool/int | `true` or 0–255 | `false` | Trim matching edges; number is threshold | | `extend` | string | `all`, `y,x`, `top,right,bottom,left` | — | Add output padding after resize | | `extendWith` | enum | `background` `copy` `repeat` `mirror` | `background` | Fill mode for `extend` | | `rotate` / `r` | float | -360–360 | — | Rotate by degrees after EXIF auto-orient | | `flip` / `flop` | bool | `1` `true` `yes` `on` | `false` | Vertical / horizontal mirror | | `blur` | int | 0–1000 | `0` | Gaussian blur sigma | | `sharpen` | bool/float | `true` or 0.000001–10 | `false` | Apply mild or sigma-based sharpening | | `median` | int | 1–25 | — | Median filter size | | `gamma` / `gammaOut` | float | 1–3 | — | Gamma correction | | `negate` | bool | `1` `true` `yes` `on` | `false` | Invert colors | | `normalize` / `normalise` | bool | `1` `true` `yes` `on` | `false` | Contrast normalization | | `threshold` | int | 0–255 | — | Convert to black/white threshold | | `brightness` / `saturation` | float | 0–10 | — | Modulate brightness/saturation | | `hue` | int | -360–360 | — | Hue rotation | | `lightness` | float | -100–100 | — | Lightness adjustment | | `tint` | color | Sharp color string | — | Apply a tint | | `grayscale` / `greyscale` | bool | `1` `true` `yes` `on` | `false` | Convert to grayscale | | `animated` / `a` | bool | `1` `true` `yes` `on` | `false` | Preserve animated GIF/WebP frames when output supports it | ## Notes [#notes] * **No upscaling by default.** Keenpix never enlarges past the source resolution unless `enlarge=1` is set. * **Source URL.** Put the absolute source URL after `/img/`, for example `/img/https://cdn.example.com/hero.jpg?project=ID&w=1200`, or send it as `/img/?url=https%3A%2F%2Fcdn.example.com%2Fhero.jpg&project=ID&w=1200`. * **`dpr`** multiplies the target dimensions — `w=400&dpr=2` renders at 800px. * **Client Hints.** `w=auto&dpr=auto` lets the browser provide `Width` or `Sec-CH-Viewport-Width` plus `Sec-CH-DPR`. `Sec-CH-Width` is already in device pixels, so Keenpix converts it back to a logical width before applying DPR and avoids double scaling. Keenpix advertises and varies by all three current headers; the older unprefixed forms are accepted for compatibility. Automatic widths and DPRs are rounded to a bounded shared ladder so a signed `auto` URL cannot be turned into unlimited cache variants by spoofing headers. Explicit numeric values remain best for deterministic prewarming. * **`fmt=auto`** depends on the project's *Auto-format* setting; when off, `auto` serves JPEG. Omitting `fmt` is treated as `auto`. It is raster negotiation only: SVG origins are rasterized and returned as AVIF, WebP, or JPEG rather than preserved as SVG. An explicit `fmt` is always honoured and disables browser negotiation for that request. Use explicit `fmt` values when your CDN cannot cache separate variants by `Accept`. * **Framework format props.** Image frameworks often map `format="avif"` or `format="webp"` directly to `fmt=avif` or `fmt=webp`. Leave those props unset when you want Keenpix to return AVIF or WebP based on browser support. * **`fmt=svg`** is explicit only. It returns `image/svg+xml` only for SVG origins, optimizes them through SVGO, and strips active content. Raster transform modifiers do not apply to SVG output. * **`q`** defaults to the project's *Default quality* (**Settings → Pipeline**). * **Watermarks are project configuration, not public query parameters.** Configure the asset, opacity, scale, position, and margin under **Settings → Pipeline**. The asset host must be allowlisted. Raster variants receive the overlay; explicit SVG output remains optimized SVG without raster compositing. ## Smart-crop research TODO [#smart-crop-research-todo] `position=attention` and `position=entropy` currently expose Sharp/libvips crop heuristics. A dedicated Keenpix subject-aware smart crop is deliberately not part of v0.3 yet. Its focal-point model, face/object behavior, privacy implications, fallbacks, and cache identity need a separate design study before implementation. Named transform presets are also intentionally outside this release. --- Markdown path: https://keenpix.com/docs/reference/responses.md # Responses & errors > Headers and status codes you can expect. Canonical HTML: [https://keenpix.com/docs/reference/responses](https://keenpix.com/docs/reference/responses) Author: Keenpix Team ## Success headers [#success-headers] | Header | Value | | --------------- | -------------------------------------------------------------------------- | | `Content-Type` | `image/avif` · `image/webp` · `image/jpeg` · `image/png` · `image/svg+xml` | | `Cache-Control` | `public, max-age=31536000, immutable` | | `Accept-CH` | `Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width` | | `Vary` | `Accept` plus current and legacy image Client Hint headers | | `Content-DPR` | Resolved DPR when `dpr=auto` (legacy client compatibility) | ## Status codes [#status-codes] | Status | Meaning | | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `200` | Optimized image returned | | `400` | Missing `project` or source URL | | `402` | Cloud only: the project's workspace has no active plan | | `403` | Source host is not on the allowlist, resolves to a private/SSRF target, or the project requires [signed URLs](/docs/concepts/signed-urls) and `sig` is missing/invalid | | `404` | Unknown `project` | | `413` | Origin image exceeds the size cap (`KEENPIX_MAX_ORIGIN_BYTES`) | | `502` | Origin fetch failed | | `503` | A proxy admission limit or an unhealthy service dependency refused the request; inspect `Retry-After` and private readiness checks | | `504` | Origin request timed out (`KEENPIX_ORIGIN_TIMEOUT_MS`) | A `403` almost always means the source host isn't on the project's allowlist — add it under **Settings → Security → Allowed hosts**. If the project requires signed URLs, a `403` also fires for a missing or invalid `sig` parameter. --- Markdown path: https://keenpix.com/docs/reference/sdk-api.md # SDK API > Authenticated JSON endpoints for trusted backend integrations. Canonical HTML: [https://keenpix.com/docs/reference/sdk-api](https://keenpix.com/docs/reference/sdk-api) Author: Keenpix Team The SDK API is for trusted backend systems and external integrations that need to manage Keenpix projects programmatically. It is separate from the public `/img/*` transform endpoint: image URLs stay keyless and are still protected by each project's allowed source hosts. Create an internal API key from **Settings -> API keys** as a super admin. Keys can be scoped to every project or to one project. Project-scoped keys can read and write only that project and cannot create new projects. Send the key with either header: ```http Authorization: Bearer X-Keenpix-Api-Key: ``` All SDK responses are JSON and set `Cache-Control: no-store`. SDK requests are recorded under **Settings -> API keys -> API activity**. Keenpix intentionally uses project-scoped API keys for this server-to-server API. It does not operate an OAuth authorization server or a separate API sandbox, and it does not publish an official CLI. Call the REST API directly or use the official `@keenpix/sdk` package. For API and integration questions, email [hi@raedbahri.com](mailto:hi@raedbahri.com) or message [WhatsApp +216 26 765 990](https://wa.me/21626765990). ## Endpoints [#endpoints] | Method | Path | Scope | Purpose | | -------- | ------------------------------------------------------ | ----------------- | -------------------------------------------------------- | | `GET` | `/api/sdk/v1/projects` | read | List projects visible to the key. | | `POST` | `/api/sdk/v1/projects` | all-project write | Create a project. | | `GET` | `/api/sdk/v1/projects/` | read | Fetch one project. | | `GET` | `/api/sdk/v1/projects//configuration` | read | Fetch integration configuration for building image URLs. | | `PATCH` | `/api/sdk/v1/projects//settings` | write | Update transform defaults. | | `POST` | `/api/sdk/v1/projects//prewarm` | write | Queue cache prewarming for source images. | | `POST` | `/api/sdk/v1/projects//domains` | write | Add an allowed source host. | | `DELETE` | `/api/sdk/v1/projects//domains?host=` | write | Remove an allowed source host. | ## Projects [#projects] Create a project with an all-project write key: ```bash curl -X POST "https://keenpix.example.com/api/sdk/v1/projects" \ -H "Authorization: Bearer $KEENPIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Storefront", "origin": "https://cdn.example.com", "allowedOrigins": ["cdn.example.com"] }' ``` Request body: | Field | Type | Required | Notes | | ---------------- | --------- | -------- | ----------------------------------------------------- | | `name` | string | yes | 1-80 characters. | | `origin` | URL | yes | `http` or `https` origin URL. | | `allowedOrigins` | string\[] | no | Hostnames to allow. URLs are normalized to hostnames. | List or fetch projects: ```bash curl "https://keenpix.example.com/api/sdk/v1/projects" \ -H "Authorization: Bearer $KEENPIX_API_KEY" curl "https://keenpix.example.com/api/sdk/v1/projects/$PROJECT_ID" \ -H "Authorization: Bearer $KEENPIX_API_KEY" ``` Project responses include the project and organization ids, origin, allowed origins, color fields, transform defaults, signing policy, watermark settings, and `createdAt`. ## Configuration [#configuration] Use the configuration endpoint when an external CMS or backend needs the canonical image base URL and the supported transform parameters. ```bash curl "https://keenpix.example.com/api/sdk/v1/projects/$PROJECT_ID/configuration" \ -H "Authorization: Bearer $KEENPIX_API_KEY" ``` ```json { "configuration": { "projectId": "cm...", "projectName": "Storefront", "origin": "https://cdn.example.com", "allowedOrigins": ["cdn.example.com"], "imageBaseUrl": "https://keenpix.example.com/img", "transformUrlTemplate": "https://keenpix.example.com/img/?project=cm...", "defaults": { "autoFormat": true, "defaultQuality": 75, "stripMetadata": true }, "supportedParameters": [ "project", "url", "w", "h", "q", "fmt", "fit", "dpr", "blur", "position", "pos", "gravity", "background", "bg", "flatten", "resize", "s", "enlarge", "kernel", "extract", "crop", "trim", "extend", "extendWith", "rotate", "r", "flip", "flop", "sharpen", "median", "gamma", "gammaOut", "negate", "normalize", "normalise", "threshold", "brightness", "saturation", "hue", "lightness", "tint", "grayscale", "greyscale", "animated", "a" ] } } ``` `imageBaseUrl` and `transformUrlTemplate` are derived from proxy headers (`X-Forwarded-Host` and `X-Forwarded-Proto`) when present, then fall back to the request origin. Configure your reverse proxy to forward the public host and protocol so third-party integrations receive the external Keenpix URL. ## Settings [#settings] Patch one or more transform defaults: ```bash curl -X PATCH "https://keenpix.example.com/api/sdk/v1/projects/$PROJECT_ID/settings" \ -H "Authorization: Bearer $KEENPIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "autoFormat": true, "defaultQuality": 82, "stripMetadata": true }' ``` | Field | Type | Notes | | ---------------- | ------- | ----------------------------------------------- | | `autoFormat` | boolean | Enables `fmt=auto` negotiation. | | `defaultQuality` | integer | `30` through `100`. | | `stripMetadata` | boolean | Removes image metadata from transformed output. | Send at least one field. ## Domains [#domains] Allowed source hosts control what `/img/*` may fetch. Add a host with JSON: ```bash curl -X POST "https://keenpix.example.com/api/sdk/v1/projects/$PROJECT_ID/domains" \ -H "Authorization: Bearer $KEENPIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "host": "assets.example.com" }' ``` Remove a host with a query string or JSON body: ```bash curl -X DELETE "https://keenpix.example.com/api/sdk/v1/projects/$PROJECT_ID/domains?host=assets.example.com" \ -H "Authorization: Bearer $KEENPIX_API_KEY" ``` Hosts are normalized to lowercase hostnames. Schemes, paths, and ports are removed when possible. ## Prewarm [#prewarm] Use prewarm after a trusted integration uploads or publishes images. Keenpix queues the requested transform variants and returns immediately with `202`; it does not wait for every transform to finish and it does not create user-facing request-log rows. Source URLs still have to pass the project's allowed-host and SSRF checks when the queued work runs. ```bash curl -X POST "https://keenpix.example.com/api/sdk/v1/projects/$PROJECT_ID/prewarm" \ -H "Authorization: Bearer $KEENPIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sources": ["https://cdn.example.com/uploads/photo.jpg"], "widths": [320, 640, 768, 960, 1280], "formats": ["avif", "webp"], "quality": 75, "fit": "cover", "dpr": 1 }' ``` Request body: | Field | Type | Default | Notes | | --------- | ---------- | ---------------------------- | ----------------------------------------- | | `src` | URL | - | One source URL. Use `src` or `sources`. | | `sources` | URL\[] | - | 1-20 source URLs. Use `src` or `sources`. | | `widths` | integer\[] | `[320, 640, 768, 960, 1280]` | 1-10 widths, each 1-5000. | | `formats` | string\[] | `["avif", "webp"]` | `auto`, `avif`, `webp`, `jpeg`, or `png`. | | `quality` | integer | project default | `30` through `100`. | | `fit` | string | `cover` | `cover`, `contain`, `fill`, or `inside`. | | `dpr` | integer | `1` | `1` through `3`. | A single prewarm request can queue at most 200 variants: `unique sources * widths * formats <= 200`. Successful response: ```json { "prewarm": { "accepted": true, "sourceCount": 1, "variantCount": 10 } } ``` ## Errors [#errors] SDK errors use this shape: ```json { "error": "Missing API key", "code": "missing_api_key", "message": "Missing API key", "resolution_hint": "Send a project-scoped key in Authorization: Bearer or X-Keenpix-Api-Key." } ``` The `error` string remains for compatibility with existing SDK versions. New integrations should branch on the stable `code`, display `message` when useful, and treat `resolution_hint` as guidance rather than executable input. | Status | When | | ------ | ---------------------------------------------------------------------------------- | | `400` | Invalid JSON, invalid URL or host, invalid settings, or too many prewarm variants. | | `401` | Missing or invalid API key. | | `403` | Project-scoped key cannot access the requested project or operation. | | `404` | Unknown project or endpoint. | | `429` | API key rate limit exceeded. | | `500` | Unexpected SDK API failure. | --- Markdown path: https://keenpix.com/docs/reference/sdk-package.md # Node SDK package > Install and use @keenpix/sdk from trusted server code for projects, configuration, domains, prewarming, and signing. Canonical HTML: [https://keenpix.com/docs/reference/sdk-package](https://keenpix.com/docs/reference/sdk-package) Author: Keenpix Team `@keenpix/sdk` is the MIT-licensed, server-side management client for the versioned `/api/sdk/v1` API. ```bash pnpm add @keenpix/sdk # or npm install @keenpix/sdk ``` Never import the management client into browser or mobile code. Its API key has control-plane permissions. ## Create a client [#create-a-client] ```ts import { createKeenpixClient } from '@keenpix/sdk' const keenpix = createKeenpixClient({ apiKey: process.env.KEENPIX_API_KEY!, baseUrl: 'https://keenpix.example.com', }) ``` The SDK uses the platform `fetch`. Pass a custom `fetch` implementation for tests, tracing, or a runtime with a nonstandard fetch implementation. ## Projects [#projects] ```ts const projects = await keenpix.listProjects() const project = await keenpix.createProject({ name: 'Marketing site', origin: 'https://assets.example.com', allowedOrigins: ['assets.example.com'], }) await keenpix.updateProject(project.id, { autoFormat: true, defaultQuality: 82, maxWidth: 2400, stripMetadata: true, watermarkEnabled: true, watermarkUrl: 'https://assets.example.com/brand/watermark.png', watermarkPosition: 'southeast', watermarkOpacity: 65, watermarkScale: 18, watermarkMargin: 20, }) ``` Use `getProject(projectId)` for one project and `getConfiguration(projectId)` for the delivery configuration consumed by framework integrations. ## Domains [#domains] ```ts await keenpix.addDomain(project.id, 'images.example.com') await keenpix.removeDomain(project.id, 'images.example.com') ``` Domain changes are authenticated control-plane operations. Public transform URLs remain keyless and are constrained by project origins and SSRF protections. ## Prewarming [#prewarming] ```ts await keenpix.prewarm(project.id, { sources: ['/hero.jpg', '/product.jpg'], widths: [640, 1280, 1920], formats: ['avif', 'webp'], quality: 82, }) ``` Prewarming enqueues deterministic BullMQ jobs. The call returns accepted source and variant counts; the worker processes them asynchronously. ## Errors [#errors] Non-2xx responses throw `KeenpixApiError` with `status` and parsed `body` fields. ```ts import { KeenpixApiError } from '@keenpix/sdk' try { await keenpix.getProject('missing') } catch (error) { if (error instanceof KeenpixApiError && error.status === 404) { // handle a missing project } } ``` ## Server-side signing [#server-side-signing] The optional `@keenpix/sdk/signing` export creates HMAC signatures with the same canonical payload as `@keenpix/core`: ```ts import { signTransformUrl } from '@keenpix/sdk/signing' const signed = signTransformUrl( 'https://images.example.com/img?url=https%3A%2F%2Fassets.example.com%2Fhero.jpg&w=auto&dpr=auto', process.env.KEENPIX_SIGNING_SECRET!, { expiresAt: Date.now() + 5 * 60 * 1000, keyVersion: 3, }, ) ``` Keep the signing secret on the server. `signTransformUrl` signs `kid`, `iat`, `exp`, Client Hint modes, and every other query value. Browser framework adapters build unsigned public URLs unless your server signs them first. --- Markdown path: https://keenpix.com/docs/self-hosting/cdn.md # CDN setup > Put Cloudflare or another CDN in front of a self-hosted Keenpix instance. Canonical HTML: [https://keenpix.com/docs/self-hosting/cdn](https://keenpix.com/docs/self-hosting/cdn) Author: Keenpix Team Keenpix runs image processing on your server and emits immutable, CDN-ready image responses. Your CDN should cache only the image transform path: ```text /img/?project=PROJECT_ID&w=1200&fmt=webp ``` Do not cache the dashboard, auth routes, docs, or health checks. ## Cloudflare [#cloudflare] 1. Create a DNS record such as `images.example.com` pointing at your VPS and enable the orange-cloud proxy. 2. Use **Full (strict)** SSL with a valid origin certificate or Let's Encrypt certificate on the VPS. 3. Create a Cache Rule matching only the image path: ```text http.host eq "keenpix.joodlab.com" and starts_with(http.request.uri.path, "/img/") ``` 4. Set the rule to **Eligible for cache**. 5. Leave **Cache key** unset on non-Enterprise plans. Cloudflare's default/standard cache key already includes the full request URI with its query string, so each `?w=` / `?fmt=` variant is cached separately. Do not enable "Ignore query string". Enable query-string sorting if available. 6. Let Cloudflare respect Keenpix's origin header, or set an edge TTL that matches the immutable image lifetime: ```http Cache-Control: public, max-age=31536000, immutable ``` Cloudflare proxying alone is not the complete setup for this app. The transform route is dynamic from Cloudflare's perspective, so add the Cache Rule above to make `/img/*` eligible for edge cache. ## Measuring Cloudflare cache [#measuring-cloudflare-cache] Keenpix origin analytics only include requests that reach the Keenpix origin. A Cloudflare edge `HIT` is served before the app runs, so it never appears in the Keenpix request log or origin rollups. Treat the origin hit-rate as the origin-shield hit-rate for Cloudflare misses, not the total end-user cache hit-rate. To see the edge side, connect the built-in edge analytics below — Keenpix pulls the Cloudflare numbers and persists them so they line up next to the origin-shield figures. For the total edge picture, use Cloudflare **Cache Analytics** filtered to the Keenpix image hostname and `/img/` path. Review cache status, top miss URLs, and data transfer served by Cloudflare versus origin. This is the fastest way to validate the cache rule and identify URLs that fragment the edge cache. ### Built-in edge analytics [#built-in-edge-analytics] Keenpix can pull the real Cloudflare edge figures for you and show **source-split cards** (Cloudflare edge vs Keenpix origin) and an hourly edge hit/miss chart on Overview and `/app/analytics`, next to the origin-shield numbers. The cards cover edge hit-rate, edge requests, and bytes served from the edge. To connect it, set the `CLOUDFLARE_*` [environment variables](/docs/self-hosting/configuration#cloudflare-edge-analytics): a Cloudflare API token scoped to **Analytics → Read**, your 32-character hex **Zone ID** (`CLOUDFLARE_ZONE_ID`), and optionally a **hostname** (`CLOUDFLARE_HOST`). Create a dedicated token at **Cloudflare → My Profile → API Tokens → Create Token** with a single permission — **Zone → Analytics → Read**, scoped to your zone. The effective status is shown read-only in the operator console at **Admin → Settings**. The optional **hostname** narrows the figures to one subdomain. A Cloudflare zone covers the whole domain and the query only filters by path (`/img/*`), so if your zone also serves other sites that use a `/img` path, set the hostname (e.g. `images.example.com`) to attribute the numbers to Keenpix alone. Leave it blank to count `/img/*` across the entire zone. #### How the edge data is persisted [#how-the-edge-data-is-persisted] Keenpix reads the Cloudflare GraphQL `httpRequestsAdaptiveGroups` dataset — the only one that supports the `/img/*` path filter with per-cache-status detail. On non-Enterprise plans that dataset only answers for roughly the **last 24 hours** and rejects wider queries. So instead of querying it live for each range, Keenpix captures the last-24h hourly series and **persists it** to its own edge-rollup table, then reconstructs edge analytics from that table for **any** range (24h / 7d / 30d / 90d). The edge cards are no longer fixed to a 24h window. Capture is **opportunistic and throttled** (about once every 15 minutes per zone/host). It runs as a best-effort background refresh when someone loads the Overview or analytics page — there is **no cron or scheduler**. The edge query also runs **off the page's critical path**, so a slow Cloudflare round-trip never blocks the page render. Each capture pulls a fresh last-24h series, so any gap shorter than 24h self-heals on the next dashboard visit. (If nobody opens the dashboard for more than 24h, that window is lost — fine for a regularly-used dashboard.) **History grows forward.** Cloudflare won't backfill far beyond the recent window, so the first capture seeds about 24h of history. The **24h** view reconciles right away; **7d** and **30d** fill in over the following days, and **90d** accumulates over time. For a range that reaches before the captured history, the edge totals still show, but the source-split cards stay origin-only and add the note *"Cloudflare edge history is still accumulating — older data for this range isn't available yet"* rather than reconcile a misleading partial total. The reconciled source-split appears only at **All projects** scope. Cloudflare has no notion of the Keenpix project id, so edge data is **zone/host-wide** for `/img/*` — per-project edge figures are not available. For deeper, per-URL reporting, ingest Cloudflare HTTP request logs or analytics as a separate edge-cache data source. The useful fields are: * `CacheCacheStatus` for `hit`, `miss`, `expired`, `revalidated`, `dynamic`, and related statuses. * `ClientRequestHost`, `ClientRequestPath`, and `ClientRequestURI` to scope records to `/img/*` and parse the Keenpix `project` query value. * `CacheResponseBytes` for bytes served from Cloudflare cache. * `CacheTieredFill` and `CacheReserveUsed` if those Cloudflare products are enabled. Do not try to derive edge-hit counts from `/app/analytics`; by design, those edge hits do not contact the origin. ## Format negotiation [#format-negotiation] The preferred URL shape puts the source URL in the path: ```text https://images.example.com/img/https://cdn.example.com/hero.jpg?project=store&w=1200&fmt=auto ``` That keeps the source file extension in Cloudflare's request path, which is important if you use Cloudflare's image variant caching with `Vary: Accept`. For the highest cache hit rate on any CDN, request explicit formats in the URL: ```text https://images.example.com/img/https://cdn.example.com/hero.jpg?project=store&w=1200&fmt=webp ``` Use `fmt=auto` only when your CDN can cache separate variants by `Accept` or has image variant support enabled. Otherwise, two browsers can ask for the same URL but need different output formats. In Nuxt Image or JoodCMS-style integrations, leaving the image component's `format` prop unset produces this auto-negotiated URL; setting `format="avif"` or `format="webp"` forces a single variant. On Cloudflare non-Enterprise plans, prefer explicit `fmt` values because custom cache key headers, including `Accept`, are Enterprise-only. SVG is fixed-format only. If you want optimized SVG bytes, request `fmt=svg`; `fmt=auto` rasterizes SVG origins and varies the raster response by `Accept`. ## Nginx [#nginx] Nginx can be the caching layer in front of Keenpix. Cache only `/img/`, and put the **full request URI** (`$request_uri` includes the path and query string) in the cache key so every `?w=` / `?fmt=` variant is stored separately. ```nginx # Declare the cache store once, at the http{} level. proxy_cache_path /var/cache/nginx/keenpix levels=1:2 keys_zone=keenpix:10m max_size=10g inactive=30d use_temp_path=off; server { listen 443 ssl; server_name images.example.com; # ssl_certificate / ssl_certificate_key ... # Cache the transform endpoint only. location /img/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache keenpix; proxy_cache_key "$scheme$host$request_uri"; proxy_cache_valid 200 30d; # safety net; Keenpix already sends immutable proxy_cache_use_stale error timeout updating; add_header X-Cache-Status $upstream_cache_status; } # Dashboard, auth, docs, and /api/health stay uncached. location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` Nginx honors the `Cache-Control: … immutable` and `Vary: Accept` headers Keenpix returns, so `fmt=auto` is cached correctly per `Accept` value. Watch `X-Cache-Status` (`MISS` then `HIT`) to confirm the cache is working. ## Caddy [#caddy] A plain Caddy reverse proxy forwards Keenpix's immutable headers untouched, so browsers and any downstream CDN still cache `/img/*`: ```text images.example.com { reverse_proxy 127.0.0.1:3000 } ``` To make Caddy itself an edge cache, build it with the [cache-handler](https://github.com/caddyserver/cache-handler) plugin (`xcaddy build --with github.com/caddyserver/cache-handler`) and cache only the image path: ```text { cache # Souin-backed; respects upstream Cache-Control. } images.example.com { @img path /img/* route { cache @img reverse_proxy 127.0.0.1:3000 } } ``` ## Fastly, Bunny, CloudFront, and others [#fastly-bunny-cloudfront-and-others] The same rules apply to any proxy or CDN: * Cache only paths that start with `/img/`. * Include the full path and full query string in the cache key. * On Cloudflare non-Enterprise plans, leave the Cache key section unset; the default key already includes the query string. * Respect `Cache-Control` from Keenpix — do not shorten or strip it. * Respect `Vary: Accept` if you use `fmt=auto`; otherwise generate explicit `fmt` values. * If you use `w=auto` or `dpr=auto`, include the `Sec-CH-DPR`, `Sec-CH-Width`, and `Sec-CH-Viewport-Width` request headers in the cache key. If your CDN plan cannot vary by those headers, keep numeric framework width ladders instead. * Keep dashboard, auth, docs, and `/api/health` uncached. If the CDN cannot vary by `Accept`, use explicit `fmt=avif`, `fmt=webp`, `fmt=jpeg`, `fmt=png`, or `fmt=svg` in generated image URLs. --- Markdown path: https://keenpix.com/docs/self-hosting/configuration.md # Configuration > Environment variables for a self-hosted instance. Canonical HTML: [https://keenpix.com/docs/self-hosting/configuration](https://keenpix.com/docs/self-hosting/configuration) Author: Keenpix Team Set these in `.env` (or your orchestrator's secrets). ## Core [#core] | Variable | Required | Default | Purpose | | ------------------------------ | ------------- | ----------------------- | ------------------------------------------------------------------------------------------------ | | `DATABASE_URL` | yes | — | Postgres connection string | | `POSTGRES_PASSWORD` | yes (compose) | — | Password for the bundled Compose Postgres service | | `BETTER_AUTH_SECRET` | yes (prod) | — | Session secret; must be 32+ random chars | | `BETTER_AUTH_URL` | no | `http://localhost:3000` | Public base URL for auth callbacks and secure-cookie behavior | | `KEENPIX_APP_URL` | no | `BETTER_AUTH_URL` | Canonical app URL for generated metadata, docs links, and OG URLs | | `KEENPIX_SUPER_ADMIN_EMAIL` | yes | — | Email for the bootstrapped super admin account | | `KEENPIX_SUPER_ADMIN_PASSWORD` | yes | — | Password for the bootstrapped super admin account | | `KEENPIX_ADMIN_EMAIL` | legacy alias | — | Backward-compatible alias for `KEENPIX_SUPER_ADMIN_EMAIL` | | `KEENPIX_ADMIN_PASSWORD` | legacy alias | — | Backward-compatible alias for `KEENPIX_SUPER_ADMIN_PASSWORD` | | `KEENPIX_RUN_MIGRATIONS` | no | `true` in Docker | Docker entrypoint switch for running migrations before start | | `KEENPIX_RUN_SEED` | no | `true` in Docker | Docker entrypoint switch for seeding the default org and super admin before start | | `KEENPIX_CACHE_DIR` | no | `./.keenpix-cache` | Where transformed images are cached on disk | | `KEENPIX_MODE` | no | `selfhost` | `selfhost` (default) or `cloud`. Self-host disables the marketing site, self-signup, and billing | | `LOG_LEVEL` | no | `info` | Server log level: `fatal`, `error`, `warn`, `info`, `debug`, `trace`, or `silent` | | `KEENPIX_LOG_DIR` | no | — | Also retain rotating evlog NDJSON files in this directory; stdout logging remains enabled | ## Browser public URLs [#browser-public-urls] These Vite variables are only needed when the browser bundle must call a non-default public app or auth URL. | Variable | Default | Purpose | | ------------------------- | ------- | ----------------------------------------- | | `VITE_KEENPIX_PUBLIC_URL` | — | Browser-facing app URL | | `VITE_KEENPIX_AUTH_URL` | — | Backward-compatible auth URL alias | | `VITE_BETTER_AUTH_URL` | — | Backward-compatible Better Auth URL alias | ## Transactional email [#transactional-email] Verification, password reset, and teammate invitation emails go through exactly **one** provider, selected with `EMAIL_PROVIDER`. Email is configured entirely from the environment — there is no in-app email settings screen. Leave `EMAIL_PROVIDER` unset to disable email (fine for a single-admin install that never invites teammates). When it is set, the selected provider's required variables must be present or the app fails to start. | Variable | Default | Purpose | | ---------------- | ------- | --------------------------------------------- | | `EMAIL_PROVIDER` | — | `postmark`, `resend`, or `smtp` (unset = off) | ### `EMAIL_PROVIDER=postmark` [#email_providerpostmark] | Variable | Default | Purpose | | ------------------------- | ---------- | ------------------------------------------------------- | | `POSTMARK_API_KEY` | — | Postmark server token (required) | | `POSTMARK_FROM` | — | Verified sender, e.g. `Keenpix ` (required) | | `POSTMARK_MESSAGE_STREAM` | `outbound` | Message stream to send on | ### `EMAIL_PROVIDER=resend` [#email_providerresend] | Variable | Default | Purpose | | ---------------- | ------- | ------------------------------------------------------- | | `RESEND_API_KEY` | — | Resend API key (required) | | `RESEND_FROM` | — | Verified sender, e.g. `Keenpix ` (required) | ### `EMAIL_PROVIDER=smtp` [#email_providersmtp] Point at any SMTP relay (a provider, or a self-hosted Postfix/Mailpit). | Variable | Default | Purpose | | ----------------- | ------- | --------------------------------------------- | | `SMTP_HOST` | — | SMTP server hostname (required) | | `SMTP_FROM_EMAIL` | — | Sender email address (required) | | `SMTP_PORT` | `587` | SMTP server port | | `SMTP_SECURE` | `false` | `true` for implicit TLS, `false` for STARTTLS | | `SMTP_USER` | — | SMTP username | | `SMTP_PASSWORD` | — | SMTP password | | `SMTP_FROM_NAME` | — | Sender display name | ## Cloudflare edge analytics [#cloudflare-edge-analytics] Optional. Wire a Cloudflare API token so keenpix can show real edge cache hit-rate alongside its origin-shield figures (see [edge analytics](/docs/self-hosting/cdn#built-in-edge-analytics)). keenpix now persists hourly edge rollups, so edge analytics covers any range and accumulates history beyond Cloudflare's \~24h adaptive window — coverage grows forward as the dashboard is used. Configure it with the environment variables below; the effective status is shown read-only at **Admin → Settings**. The token only needs the zone-scoped **Analytics → Read** permission. | Variable | Default | Purpose | | ---------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CLOUDFLARE_API_TOKEN` | — | Zone-scoped API token with Analytics → Read | | `CLOUDFLARE_ZONE_ID` | — | 32-character hex zone id for the image hostname | | `CLOUDFLARE_HOST` | — | Optional. A Cloudflare zone can serve several subdomains and edge analytics only filter by path (`/img/*`), so set this to the keenpix hostname (e.g. `images.example.com`) when the zone also hosts other sites that use a `/img` path. Leave blank to count the whole zone. | ## Limits & performance [#limits--performance] | Variable | Default | Purpose | | ------------------------------------ | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `KEENPIX_CACHE_MAX_BYTES` | transform default: `2147483648` (2GB); Docker/Coolify: `8589934592` (8GB) | Disk cache LRU cap | | `KEENPIX_CACHE_STALE_MS` | `86400000` (24h) | Serve stale disk entries immediately and refresh them in the background; `0` disables stale refresh | | `KEENPIX_MEMORY_CACHE_MAX_BYTES` | transform default: `67108864` (64MB); Docker/Coolify: `268435456` (256MB) | In-process hot variant LRU cap; `0` disables memory caching | | `KEENPIX_CACHE_REDIS_URL` | Compose: `redis://dragonfly:6379` | Shared Dragonfly variant tier; separate setting from the BullMQ queue URL | | `KEENPIX_CACHE_DRAGONFLY_MAX_BYTES` | `536870912` (512MB) | Dragonfly variant LRU budget | | `KEENPIX_CACHE_DELETE_AFTER_MS` | `2592000000` (30d) | Terminal age after which a variant is deleted from every tier; `0` disables | | `KEENPIX_CACHE_S3_BUCKET` | — | Shared S3/R2/MaxIO cache bucket | | `KEENPIX_CACHE_S3_ENDPOINT` | — | S3-compatible endpoint | | `KEENPIX_CACHE_S3_REGION` | `auto` | S3-compatible region | | `KEENPIX_CACHE_S3_ACCESS_KEY_ID` | — | S3-compatible access key | | `KEENPIX_CACHE_S3_SECRET_ACCESS_KEY` | — | S3-compatible secret key | | `KEENPIX_MAX_ORIGIN_BYTES` | `52428800` (50MB) | Max bytes pulled from an origin before `413` | | `KEENPIX_MAX_WATERMARK_BYTES` | `5242880` (5MB) | Max bytes pulled for a project watermark asset | | `KEENPIX_QUEUE_URL` | `redis://127.0.0.1:6379` | BullMQ connection URL; Compose uses Dragonfly | | `KEENPIX_WORKBENCH_USERNAME` | — | Basic-auth username for the internal worker dashboard; set with the password | | `KEENPIX_WORKBENCH_PASSWORD` | — | Basic-auth password for the internal worker dashboard; set with the username | | `KEENPIX_WORKER_SECRET` | — | 32+ character secret for worker-to-app callbacks | | `KEENPIX_WORKER_CONCURRENCY` | `4` | Concurrent prewarm jobs per worker process | | `KEENPIX_WORKER_PORT` | `3001` | Internal health and Workbench listener for the worker container; Compose does not publish it | | `KEENPIX_WORKER_TIMEOUT_MS` | `60000` | Maximum transform wait for one prewarm job | | `KEENPIX_TRANSFORM_URL` | — | Internal transform service URL used by app and worker | | `KEENPIX_TRANSFORM_PORT` | `3002` | Transform HTTP listener | | `KEENPIX_MAX_INPUT_PIXELS` | `50000000` | Decode-time pixel ceiling (decompression-bomb guard) | | `KEENPIX_MAX_DIMENSION` | `4096` | Longest output side when a request gives no `w`/`h` | | `KEENPIX_ORIGIN_TIMEOUT_MS` | `10000` | Per-attempt origin fetch timeout before `504` | ## Compose images and ports [#compose-images-and-ports] | Variable | Default | Purpose | | ------------------------- | -------------------------------------------- | ---------------------------------------- | | `KEENPIX_APP_IMAGE` | `ghcr.io/lord007tn/keenpix-app:latest` | Control-plane image | | `KEENPIX_TRANSFORM_IMAGE` | `ghcr.io/lord007tn/keenpix-transform:latest` | Transform data-plane image | | `KEENPIX_WORKER_IMAGE` | `ghcr.io/lord007tn/keenpix-worker:latest` | Worker and Workbench image | | `KEENPIX_DOCS_IMAGE` | `ghcr.io/lord007tn/keenpix-docs:latest` | Standalone docs image | | `KEENPIX_PORT` | `3000` | Published app port in self-host Compose | | `KEENPIX_DOCS_PORT` | `3003` | Published docs port in self-host Compose | Pin all four images to the same release tag in production. --- Markdown path: https://keenpix.com/docs/self-hosting/coolify.md # Coolify > Deploy the complete Keenpix stack with generated secrets, domains, health checks, and independent services. Canonical HTML: [https://keenpix.com/docs/self-hosting/coolify](https://keenpix.com/docs/self-hosting/coolify) Author: Keenpix Team Use `docker-compose.coolify.yml` for a normal self-hosted Coolify deployment. It references published images and uses Coolify magic variables for generated credentials. ## Create the resource [#create-the-resource] 1. Create a **Docker Compose** resource from the Keenpix repository. 2. Select `docker-compose.coolify.yml`. 3. Assign a domain to `SERVICE_URL_APP` and a docs domain to `SERVICE_URL_DOCS`. 4. Set `KEENPIX_SUPER_ADMIN_EMAIL`. 5. Deploy. Coolify generates: * `SERVICE_USER_POSTGRES` and `SERVICE_PASSWORD_64_POSTGRES` * `SERVICE_HEX_64_KEENPIX` for Better Auth * `SERVICE_HEX_64_KEENPIX_WORKER` for internal worker authentication * `SERVICE_USER_WORKBENCH` and `SERVICE_PASSWORD_64_WORKBENCH` for Workbench basic auth * `SERVICE_USER_MAXIO` and `SERVICE_PASSWORD_64_MAXIO` for the fallback object cache * `SERVICE_PASSWORD_64_ADMIN` for the initial super-admin password You may override Workbench with `KEENPIX_WORKBENCH_USERNAME` and `KEENPIX_WORKBENCH_PASSWORD`, but both must be set together. ## Service routing [#service-routing] Only `app:3000` and `docs:3003` need public domains. Keep `transform:3002`, `worker:3001`, Postgres, Dragonfly, and MaxIO private. If you expose Workbench through a private operator domain, keep its basic auth enabled and add Coolify access controls in front of it. ## Health checks [#health-checks] Coolify should show these containers as healthy: * app: `GET /api/health` * transform: `GET /health/ready` (database plus configured cache tiers) * worker: `GET /health/ready` (queue connectivity) * docs: `GET /health` * Postgres: `pg_isready` ## Cloud deployment [#cloud-deployment] `docker-compose.production.yml` is the maintained source-build stack for keenpix.com. It adds ClickHouse, MaxIO, Mailpit, usage reporting, and Postgres backups. Self-hosters should start with `docker-compose.coolify.yml` and add those capabilities only when their scale requires them. --- Markdown path: https://keenpix.com/docs/self-hosting/docker-compose.md # Docker Compose > Production-ready self-host deployment, generated secrets, images, ports, volumes, health checks, and upgrades. Canonical HTML: [https://keenpix.com/docs/self-hosting/docker-compose](https://keenpix.com/docs/self-hosting/docker-compose) Author: Keenpix Team The root `docker-compose.yml` is the supported self-host stack. It uses published GHCR images by default, so self-hosters do not need Node, pnpm, or a source build toolchain. ## Prepare the environment [#prepare-the-environment] ```bash git clone https://github.com/lord007tn/keenpix.git cd keenpix cp .env.example .env ``` Set at least: ```bash POSTGRES_PASSWORD=replace-with-a-long-random-password BETTER_AUTH_SECRET=replace-with-64-random-hex-characters KEENPIX_WORKER_SECRET=replace-with-a-different-64-random-hex-value KEENPIX_SUPER_ADMIN_EMAIL=admin@example.com KEENPIX_SUPER_ADMIN_PASSWORD=replace-with-a-long-random-password ``` Generate secrets with `openssl rand -hex 32`. Never reuse the auth secret as the worker secret. MaxIO is private to the Compose network and has internal fallback credentials so the cache works on the first boot. Set `KEENPIX_CACHE_S3_ACCESS_KEY_ID` and `KEENPIX_CACHE_S3_SECRET_ACCESS_KEY` when the Docker network is shared with untrusted workloads, or set the complete S3 block to use Cloudflare R2. ## Start and verify [#start-and-verify] ```bash docker compose pull docker compose up -d docker compose ps docker compose logs -f app transform worker ``` The dashboard is available at `http://localhost:3000`; docs default to `http://localhost:3003`. Set `KEENPIX_PORT` and `KEENPIX_DOCS_PORT` to change host ports. The app waits for Postgres and transform readiness. The worker waits for transform and Dragonfly. The transform service waits for Dragonfly and MaxIO. Docker restarts failed services with `unless-stopped`. ## Images [#images] | Variable | Default image | | ------------------------- | -------------------------------------------- | | `KEENPIX_APP_IMAGE` | `ghcr.io/lord007tn/keenpix-app:latest` | | `KEENPIX_TRANSFORM_IMAGE` | `ghcr.io/lord007tn/keenpix-transform:latest` | | `KEENPIX_WORKER_IMAGE` | `ghcr.io/lord007tn/keenpix-worker:latest` | | `KEENPIX_DOCS_IMAGE` | `ghcr.io/lord007tn/keenpix-docs:latest` | Pin all four to the same `vX.Y.Z` tag for production. Do not mix application versions across one deployment. ## Persistent volumes [#persistent-volumes] | Volume | Data | | ------------------- | ------------------------------------------ | | `keenpix_pg` | Postgres control-plane data | | `keenpix_dragonfly` | BullMQ jobs and Dragonfly snapshots | | `keenpix_maxio` | Durable S3-compatible transformed variants | | `keenpix_cache` | Transformed image disk cache | Postgres must be backed up. MaxIO and the image cache can be rebuilt. Queue data is operationally useful but is not the system of record. To replace MaxIO with Cloudflare R2, set the five `KEENPIX_CACHE_S3_*` variables; no Compose topology change is required. ## Upgrade [#upgrade] ```bash docker compose pull docker compose up -d --remove-orphans docker compose ps ``` The app entrypoint applies Prisma migrations before accepting traffic. Back up Postgres before upgrading, read release notes, and roll back by restoring both the prior image tags and a compatible database backup. --- Markdown path: https://keenpix.com/docs/self-hosting/health-and-operations.md # Health and operations > Readiness, liveness, Workbench, logging, backups, and the first checks when a deployment is unhealthy. Canonical HTML: [https://keenpix.com/docs/self-hosting/health-and-operations](https://keenpix.com/docs/self-hosting/health-and-operations) Author: Keenpix Team ## Probe matrix [#probe-matrix] | Service | Endpoint | Healthy response | | --------- | ------------------- | ----------------------------------------------- | | App | `GET /api/health` | HTTP 200 with application and dependency status | | Transform | `GET /health/live` | Process is accepting requests | | Transform | `GET /health/ready` | Required storage and database probes pass | | Worker | `GET /health/live` | Worker HTTP process is running | | Worker | `GET /health/ready` | Queue and transform dependencies are usable | | Docs | `GET /health` | Documentation server is running | Use liveness to restart a stuck process. Use readiness to decide whether it should receive traffic. The transform and worker probes are service-local operator endpoints on ports `3002` and `3001`. The bundled Compose files keep both ports on the private network, so `/health/live` and `/health/ready` on the public app or delivery hostname are expected to return `404`. Run the probes inside their containers: ```bash docker compose exec transform curl --fail --silent http://127.0.0.1:3002/health/live docker compose exec transform curl --fail --silent http://127.0.0.1:3002/health/ready docker compose exec worker node -e "fetch('http://127.0.0.1:3001/health/ready').then(r => { if (!r.ok) process.exit(1) })" ``` ## Workbench [#workbench] Workbench is hosted by `apps/worker` and shows queue state, jobs, retries, and failures. It uses HTTP basic auth from `KEENPIX_WORKBENCH_USERNAME` and `KEENPIX_WORKBENCH_PASSWORD`. Coolify generates credentials automatically. Workbench is an operator surface. Do not expose it without authentication, and prefer a private network, VPN, or access proxy. ## Logs [#logs] ```bash docker compose logs --since=15m app docker compose logs --since=15m transform docker compose logs --since=15m worker ``` evlog writes structured output to the container terminal. Set `KEENPIX_LOG_DIR` only when you also mount a persistent directory and have a retention plan. ## Failure order [#failure-order] 1. Check `docker compose ps` for the first unhealthy dependency. 2. Check Postgres credentials and migrations. 3. Check transform readiness and available cache-disk space. 4. Check Dragonfly and worker queue connectivity. 5. Confirm `KEENPIX_WORKER_SECRET` matches between app, transform, and worker. 6. Confirm the origin host is allowed and resolvable from the transform container. Back up Postgres before upgrades. Cache volumes are disposable; Postgres is not. --- Markdown path: https://keenpix.com/docs/self-hosting.md # Deploy > Deploy Keenpix with Docker and verify the stateful parts. Canonical HTML: [https://keenpix.com/docs/self-hosting](https://keenpix.com/docs/self-hosting) Author: Keenpix Team Last reviewed: 2026-09-01 Keenpix deploys as four focused Node applications plus Postgres and Dragonfly. The default Compose file exposes the control plane on port `3000` and the standalone docs site on `3003`; the transform and worker services remain private on the Compose network. Weighing self-host against the hosted option? See [self-hosted vs managed image optimization](/blog/self-host-vs-managed-image-optimization). ## Deploy a pinned release [#deploy-a-pinned-release]
    
      {selfHostingDeployCommand}
    
  
`docker-compose.yml` ships the app, transform data plane, BullMQ worker with Workbench, docs, Postgres, and Dragonfly. On boot the app runs `prisma migrate deploy`, optionally seeds the default organization and super admin, then starts as a non-root user. Check the [release page](https://github.com/lord007tn/keenpix/releases) before changing the four image variables; keeping explicit tags prevents a later pull from replacing only part of the running release. ## After deploy [#after-deploy] 1. Sign in as `KEENPIX_SUPER_ADMIN_EMAIL` with `KEENPIX_SUPER_ADMIN_PASSWORD`. 2. Create a project pointed at your image origin. 3. Add your source host under **Settings → Security → Allowed hosts**. 4. Invite operators from the **Admin console → Staff** (`/admin/staff`) and copy the invitation link. Transactional email is configured entirely through the environment (`EMAIL_PROVIDER` + its provider vars) — there is no in-app email screen, and those env values are the only source. With email configured, the invite is also sent automatically. 5. Request `/img/https://your-origin.example/photo.jpg?project=ID&w=1200&fmt=webp` — no API key required for transform URLs. ## Architecture [#architecture] ```text Browser/CDN ──▶ app:3000 ──▶ transform:3002 ──▶ customer origin │ │ │ └── memory + disk cache volume ├── Postgres └── Dragonfly ──▶ worker:3001 Operator ──▶ docs:3003 ``` Scale the transform and worker independently. For multiple transform replicas, configure a shared S3-compatible cache such as R2 or MaxIO. See [Docker Compose](/docs/self-hosting/docker-compose), [Coolify](/docs/self-hosting/coolify), [CDN setup](/docs/self-hosting/cdn) for Cloudflare, Nginx, and Caddy caching rules, [Deployment presets](/docs/self-hosting/presets) for ready-made `.env` files, and [Configuration](/docs/self-hosting/configuration) for every environment variable. --- Markdown path: https://keenpix.com/docs/self-hosting/presets.md # Deployment presets > Copy-paste .env starting points for local, single-node, and CDN-fronted deploys. Canonical HTML: [https://keenpix.com/docs/self-hosting/presets](https://keenpix.com/docs/self-hosting/presets) Author: Keenpix Team Three ready-made starting points for `.env`. Every variable is documented on the [Configuration](/docs/self-hosting/configuration) page — these presets just bundle sensible values for common deployment shapes. Always generate your own secret with `openssl rand -hex 32`. With the bundled `docker-compose.yml`, set `POSTGRES_PASSWORD` and Compose builds `DATABASE_URL` for the app automatically. Only set `DATABASE_URL` yourself for a non-Docker run (`pnpm start`) or an external database. ## Local-only (evaluation) [#local-only-evaluation] Running on your own machine to try Keenpix. No public URL, no CDN. ```bash POSTGRES_PASSWORD="postgres" BETTER_AUTH_SECRET="" BETTER_AUTH_URL="http://localhost:3000" KEENPIX_SUPER_ADMIN_EMAIL="admin@example.com" KEENPIX_SUPER_ADMIN_PASSWORD="" ``` * Plain `http` on localhost, so secure cookies stay off — fine for local only. * No email needed: self-host is invite-only. Sign in with `KEENPIX_SUPER_ADMIN_EMAIL` / `KEENPIX_SUPER_ADMIN_PASSWORD` — no OTP or verification for the bootstrap operator. ## Single-node production [#single-node-production] One server answers both the dashboard and every image transform over HTTPS. No separate CDN. ```bash POSTGRES_PASSWORD="" BETTER_AUTH_SECRET="" BETTER_AUTH_URL="https://keenpix.example.com" KEENPIX_APP_URL="https://keenpix.example.com" KEENPIX_SUPER_ADMIN_EMAIL="you@example.com" KEENPIX_SUPER_ADMIN_PASSWORD="" # Deliver staff invitations and OTP codes via SMTP (or use postmark / resend). EMAIL_PROVIDER="smtp" SMTP_HOST="smtp.example.com" SMTP_PORT="587" SMTP_USER="..." SMTP_PASSWORD="..." SMTP_FROM_EMAIL="keenpix@example.com" # This box answers transforms and runs durable prewarm jobs. KEENPIX_CACHE_MAX_BYTES="10737418240" # 10 GB KEENPIX_WORKER_SECRET="" KEENPIX_WORKER_CONCURRENCY="4" ``` * An `https://` URL turns on secure cookies automatically. * Size `KEENPIX_CACHE_MAX_BYTES` to the disk volume; the in-memory LRU (`KEENPIX_MEMORY_CACHE_MAX_BYTES`) keeps the hottest variants in RAM. * Set `KEENPIX_WORKER_CONCURRENCY` for the prewarm worker. Normal image requests remain synchronous HTTP work and are protected by origin/input limits. ## CDN-fronted production [#cdn-fronted-production] A CDN caches `/img/*` and absorbs repeat traffic, so Keenpix only does cold transforms. See [CDN setup](/docs/self-hosting/cdn). ```bash POSTGRES_PASSWORD="" BETTER_AUTH_SECRET="" # Point auth at the DASHBOARD host, not the image host the CDN serves. BETTER_AUTH_URL="https://admin.keenpix.example.com" KEENPIX_APP_URL="https://admin.keenpix.example.com" KEENPIX_SUPER_ADMIN_EMAIL="you@example.com" KEENPIX_SUPER_ADMIN_PASSWORD="" EMAIL_PROVIDER="smtp" SMTP_HOST="smtp.example.com" SMTP_PORT="587" SMTP_USER="..." SMTP_PASSWORD="..." SMTP_FROM_EMAIL="keenpix@example.com" # Cloudflare absorbs repeat traffic, but the origin is still the shared shield # for edge cold misses and tiered-cache fills. KEENPIX_CACHE_MAX_BYTES="8589934592" # 8 GB KEENPIX_MEMORY_CACHE_MAX_BYTES="268435456" # 256 MB KEENPIX_CACHE_STALE_MS="86400000" # 24 hours KEENPIX_WORKER_SECRET="" KEENPIX_WORKER_CONCURRENCY="4" ``` Scope the CDN's cache rule to `/img/*` only, or keep the dashboard on a host the CDN does not cache. Caching `/app` or `/api/auth` breaks sign-in. * Use omitted `fmt` / `fmt=auto` only when the CDN can vary by `Accept`. Otherwise, prefer explicit `fmt=webp` / `fmt=avif` in generated URLs to maximize edge hit rate, knowing that an explicit format will not fall back for unsupported browsers. * Use explicit `fmt=svg` for optimized SVG delivery. Omitted `fmt` / `fmt=auto` rasterizes SVG origins instead of preserving SVG output. * Size the origin disk cache to the image working set that still reaches Keenpix after Cloudflare. For small sites 2 GB can be enough; for high-cardinality hotel/CMS libraries, start around 8 GB and watch Operations for eviction churn. --- Markdown path: https://keenpix.com/blog/agent-assisted-image-cdn-integration.md # Agent-assisted image CDN integration without blind trust > Give a coding agent public, bounded contracts for image delivery, then verify generated URLs, security boundaries, responsive behavior, and rollback. Canonical HTML: [https://keenpix.com/blog/agent-assisted-image-cdn-integration](https://keenpix.com/blog/agent-assisted-image-cdn-integration) Author: Raed Bahri Published: 2026-09-02 Last reviewed: 2026-09-03 A coding agent can accelerate an image CDN integration when it receives the exact public contract, a bounded task, representative fixtures, and explicit acceptance tests. It should not receive production secrets, private origins, customer data, browser sessions, or permission to deploy by default. Treat generated code as an untrusted change until the application, browser, delivery service, and origin behavior are verified. ## Start with the smallest useful context [#start-with-the-smallest-useful-context] Give the agent: 1. The framework and repository instructions it must follow. 2. The public integration guide for that framework. 3. The transform endpoint and parameter reference. 4. The current origin and URL patterns with secrets removed. 5. The finite responsive width and quality policy. 6. Security requirements: allowed origins, signed delivery, maximum dimensions, and private-asset exclusions. 7. The exact tests and screenshots required. 8. A hard scope boundary: no credentials, production change, deployment, indexing request, or unrelated refactor. Do not begin by feeding an agent an entire private workspace. More context can increase leakage and make the task less precise. ## A public-safe integration brief [#a-public-safe-integration-brief] ```markdown Replace the product-card image URL builder with the documented image delivery endpoint. Preserve alt text, intrinsic dimensions, lazy-loading behavior, and the existing fallback. Use only widths 320, 640, and 960. Do not change uploads, storage, authentication, DNS, or deployment. Acceptance: - the browser selects no candidate materially larger than the rendered slot; - output Content-Type matches the requested/negotiated format; - invalid sources and dimensions fail safely; - the previous URL builder remains available behind the local configuration; - desktop and mobile screenshots show no crop or layout regression; - no secrets or private URLs appear in the diff or generated artifacts. ``` Replace the example widths and boundaries with your actual layout and product policy. ## Use discoverable, canonical sources [#use-discoverable-canonical-sources] A useful public documentation surface offers: * a concise `llms.txt` map; * complete Markdown for deeper retrieval; * a stable `.md` form for each canonical knowledge page; * `Accept: text/markdown` when the same canonical can negotiate representations; * `rel="alternate" type="text/markdown"` pointing to the page Markdown; * `rel="describedby"` pointing to the covering `llms.txt`; * an OpenAPI document for machine-readable API boundaries. These signals improve deterministic discovery for clients that implement them. The August 2026 llms.txt v2 document is a proposal and convention, not a Google ranking factor or a promise that any crawler will ingest, cite, or rank the content. Keep normal HTML, canonicals, sitemaps, internal links, and accessible page structure healthy. ## Keep public crawling separate from private tools [#keep-public-crawling-separate-from-private-tools] Robots rules should allow public learning and documentation while excluding authenticated application and sensitive API routes. OpenAI's current publisher guidance says OAI-SearchBot access is required for eligibility in ChatGPT search summaries and snippets, while placement is not guaranteed. User-agent strings can be spoofed; infrastructure that makes trust decisions should verify published IP ranges or a provider's verified-bot signal rather than grant privileges from the name alone. Crawler access never authorizes an API operation. A public documentation crawler should receive the same public HTML or Markdown as any anonymous reader, not a special route into `/app/`, account data, or administrative APIs. ## Require the agent to show its work [#require-the-agent-to-show-its-work] The review bundle should include: * changed files and why each belongs in scope; * the source URL or contract used for every non-obvious behavior; * test results and exact commands; * sample generated URLs with sensitive values replaced; * desktop and mobile screenshots; * response status, `Content-Type`, cache headers, and decoded dimensions; * the rollback switch and a test that exercises it; * explicit unknowns and unsupported capabilities. Reject claims such as “faster,” “secure,” “zero downtime,” or “saves 40%” unless the task produced the corresponding evidence. ## Offline mocks prove contracts, not provider behavior [#offline-mocks-prove-contracts-not-provider-behavior] An offline mock is useful for deterministic questions: Did the loader normalize a width? Did it preserve the source path? Does a rejected parameter return the expected application error? Can the old URL builder be restored? Keep fixtures small, explicit, and versioned with the test. A mock cannot prove that a CDN varies its cache by `Accept`, that a purge propagated, that an origin credential works, that a deployed edge returns the intended MIME type, or that a browser selects and renders the right responsive candidate. It also cannot establish production latency, availability, cache-hit rate, or billing. Avoid teaching the mock undocumented provider behavior merely to make an assertion pass. Split the evidence into layers: | Evidence | What it can prove | What remains unproven | | ------------------------------- | ----------------------------------------------------------- | ----------------------------------------------- | | Unit test with offline fixture | URL construction, finite parameters, fallback control flow | Network, CDN, origin, rendered pixels | | Local integration service | Request/response contract and decoder behavior for fixtures | Provider edge and production identity | | Raw deployed HTTP probe | Status, headers, bytes, and cache signals at that location | Browser selection and visual layout | | Rendered desktop/mobile browser | Chosen `currentSrc`, dimensions, crop, and visible result | Global edge behavior or production-wide metrics | Require the agent to label each result accordingly. Keep offline mocks in the normal test suite, then run authenticated or production probes only with explicit authority and scrubbed outputs. A successful mock should never be rewritten in a report as “the CDN was verified.” Use the [versioned URL guide](/blog/cache-invalidation-versioned-image-urls) to define cache assertions and the [performance measurement guide](/blog/reproducible-image-performance-measurement) to separate cold, warm, and browser evidence. ## Failure modes to test [#failure-modes-to-test] | Agent mistake | Verification | | -------------------------------------------------- | ---------------------------------------------------------------- | | Invents a parameter or endpoint | compare with the versioned endpoint/OpenAPI contract | | Embeds an administrative key in client code | scan built client assets and the complete diff for key patterns | | Sends a private source URL to a public transformer | test the documented origin-auth boundary and threat model | | Generates arbitrary widths | assert the finite width set and inspect browser `currentSrc` | | Removes intrinsic dimensions | test layout shift in desktop and mobile views | | Changes cache identity accidentally | compare URL normalization, `Vary`, and repeated response headers | | Claims success from unit tests only | run the rendered browser and HTTP acceptance checks | | Cannot reverse the change | exercise the old URL generator with the new path unavailable | ## Using Keenpix with a coding agent [#using-keenpix-with-a-coding-agent] Start with these public sources: * [Keenpix Learn](/learn) * [Framework guides](/docs/frameworks) * [Transform endpoint reference](/docs/reference/endpoint) * [OpenAPI specification](/openapi.json) * [Concise LLM index](/llms.txt) * [Complete Markdown collection](/llms-full.txt) Project operations require a project-scoped server-side key; normal image delivery URLs remain keyless after configuration. The public retrieval sources are not credentials or execution authority. Keenpix is not the right fit if the integration needs video, a DAM, a vendor-managed private origin connection, arbitrary origin authorization headers, OAuth, a public sandbox, or an official CLI. Use the product that owns those requirements, or keep the agent focused on the supported subset. ## Licensing boundary [#licensing-boundary] Keenpix's repository license governs the source code under its terms. It does not by itself define a separate machine-readable license for all public editorial content. This tranche does not invent an RSL policy. Adopting RSL or another content-licensing declaration requires an explicit legal decision and approved text. ## Sources and limitations [#sources-and-limitations] * [The `/llms.txt` file, v2 proposal](https://llmstxt.org/), reviewed September 2, 2026. * [OpenAI publishers and developers FAQ](https://help.openai.com/en/articles/12627856-publishers-and-developers-faq), reviewed September 2, 2026. * [OpenAI SearchBot IP ranges](https://openai.com/searchbot.json), reviewed September 2, 2026. * [OpenAPI Specification 3.1.1](https://spec.openapis.org/oas/v3.1.1.html), reviewed September 2, 2026. Crawler, browser-agent, and proposal behavior can change. Recheck primary documentation before changing a firewall, robots policy, or public contract. This guide does not grant an agent authority beyond the user's explicit task. ## Related guides [#related-guides] * [Safe image CDN rollouts and rollbacks](/blog/safe-image-cdn-rollouts-and-rollbacks) * [Private origin security boundaries](/blog/private-image-origins-security-boundaries) * [Developer resources](/developers) --- Markdown path: https://keenpix.com/blog/avif-vs-webp-production-caching.md # AVIF vs WebP in production: choose with your cache > Compare AVIF and WebP without borrowed benchmarks, then configure format negotiation and CDN cache variants without serving the wrong bytes. Canonical HTML: [https://keenpix.com/blog/avif-vs-webp-production-caching](https://keenpix.com/blog/avif-vs-webp-production-caching) Author: Raed Bahri Published: 2026-08-15 Last reviewed: 2026-09-03 The dangerous part of `AVIF vs WebP` is not picking the format with the smallest file in a test folder. It is putting two different responses behind one URL, then letting a cache forget which browser asked for which format. Keenpix can return AVIF, WebP, or JPEG from the same `fmt=auto` URL. That is convenient only when every cache in the path understands the variation. If the cache does not, use an explicit format in the URL. ## Start with one fixed test set [#start-with-one-fixed-test-set] Do not compare an AVIF thumbnail at one quality with a WebP hero at another. Choose a small set that resembles the images you actually serve: * one photograph with texture and gradients; * one product image with a clean background; * one image with transparency, if your catalog uses it. Keep source pixels, output dimensions, crop, and quality fixed. Record the output bytes and inspect the result at normal size and at 200% zoom. A lower byte count loses its value when text edges, skin, or product colors look wrong. Keenpix passes the project quality value to Sharp for both formats. It currently sets AVIF effort to `3` to reduce encoding work when the requested variant is not in cache. That is an operating choice, not proof that one format wins for every image. ## What fmt=auto does [#what-fmtauto-does] With automatic formatting enabled for the project, Keenpix reads the request's `Accept` header in this order: 1. return AVIF when the header includes `image/avif`; 2. otherwise return WebP when it includes `image/webp`; 3. otherwise return JPEG. The response includes `Vary: Accept`. You can check each branch without changing the URL: ```bash URL='https://images.example.com/img/https://assets.example.com/hero.jpg?project=store&w=1200&q=80&fmt=auto' curl -sS -D avif.headers -o avif.bin \ -H 'Accept: image/avif,image/webp,image/*' "$URL" curl -sS -D webp.headers -o webp.bin \ -H 'Accept: image/webp,image/*' "$URL" curl -sS -D jpeg.headers -o jpeg.bin \ -H 'Accept: image/jpeg,image/*' "$URL" wc -c avif.bin webp.bin jpeg.bin ``` Read `Content-Type`, `Vary`, `Cache-Control`, and the byte counts. Open the three files too. The command tests negotiation and size, but it cannot judge visual damage for you. ## The cache decides whether auto is safe [#the-cache-decides-whether-auto-is-safe] These two requests share a URL but must not share a stored object: ```text Accept: image/avif,image/webp,image/* Accept: image/webp,image/* ``` A browser cache understands `Vary: Accept`. An outer CDN may require an explicit variant rule or cache-key setting. Cloudflare's Vary for Images feature also requires a compatible image extension in the path. Cloudflare documents the feature for Pro, Business, and Enterprise plans, and requires a configured variants rule; returning `Vary: Accept` does not enable it by itself. Run two cold requests with different `Accept` headers, then repeat them. Confirm both the MIME type and the CDN cache status. If either format returns the other's MIME type after a hit, disable automatic negotiation at that layer. The simpler option puts the format in the URL: ```text /hero.jpg?project=store&w=1200&q=80&fmt=avif /hero.jpg?project=store&w=1200&q=80&fmt=webp ``` Now each format has a distinct cache key without depending on a request header. Your application must choose a supported format, usually through `` sources or framework-generated URLs. ## A practical rule for choosing [#a-practical-rule-for-choosing] Use `fmt=auto` when the CDN has proven variant support. If that CDN is Cloudflare, retain the source extension in the request path to meet its Vary for Images rules. That lets a current browser ask for AVIF while the JPEG fallback remains available. Use explicit WebP when the delivery stack needs one predictable modern format and the audience test supports it. Use explicit AVIF only when the fixed-image comparison shows a useful byte reduction at acceptable quality and the caller already handles fallback. For a screenshot with small text, a higher-quality WebP can be the better result even when an AVIF file is smaller. Animation, transparency, and progressive display deserve separate test cases. Do not turn a result from still photographs into a policy for every asset type. ## When an AVIF transform fails [#when-an-avif-transform-fails] Do not assume an automatic browser fallback can rescue a failed `fmt=auto` response. Negotiation chooses one server response for that request. If AVIF encoding fails after the request selects AVIF, the browser receives that failure; it does not automatically replay the same URL with a WebP-only `Accept` header. Keenpix's current transform path converts most Sharp decode or encode failures into a generic `502 Origin is not a valid image` response. That safe public message deliberately does not expose native decoder detail, so diagnose with bounded evidence: 1. Request `fmt=avif`, `fmt=webp`, and `fmt=jpeg` as three explicit URLs using the same source, dimensions, crop, and quality. 2. Record status, `Content-Type`, cache status, response body, and a correlation identifier for each request. 3. Repeat with a known-good fixture. If all formats fail, inspect source fetch, declared type, truncation, pixel limits, and decoder support before blaming AVIF. 4. If only AVIF fails, preserve the exact source digest, transform parameters, Sharp/libvips versions, and sanitized server-side error. Do not publish the source URL when it is private. 5. Choose a product policy: fail the variant, route callers to an explicit WebP/JPEG URL, or remove AVIF from negotiation until the codec path is corrected. Keep fallback identity cache-safe. A `` element with distinct explicit-format URLs gives the browser a declarative fallback path when a format is unsupported. It still will not necessarily fall through when the chosen resource returns an ordinary server error, so test the actual browsers and markup you support. The [symptom-based troubleshooting guide](/blog/image-delivery-troubleshooting-by-symptom) covers fetch and response isolation; the [versioned URL guide](/blog/cache-invalidation-versioned-image-urls) keeps corrected source bytes from colliding with cached failed or old variants. ## When format work is premature [#when-format-work-is-premature] Fix oversized dimensions before spending time on codec differences. A 2400-pixel WebP displayed in a 600-pixel slot can waste more than the format choice saves. Check responsive widths, `sizes`, crop, and quality first, then compare codecs using the bytes that remain. You also do not need Keenpix to use AVIF or WebP. Static builds and framework optimizers can produce both. An image service becomes useful when the variants depend on runtime dimensions, changing origins, or a shared URL policy across applications. ## Sources and verification [#sources-and-verification] This article was checked on August 15, 2026 against the [AVIF 1.2.0 specification](https://aomedia.org/docs/AV1%20Image%20File%20Format%20%28AVIF%29%20v1.2.0.pdf), the official [WebP container specification](https://developers.google.com/speed/webp/docs/riff_container), the [Sharp AVIF and WebP output options](https://sharp.pixelplumbing.com/api-output/), [Cloudflare's Vary for Images requirements](https://developers.cloudflare.com/cache/advanced-configuration/vary-for-images/), and the [Keenpix parameter reference](/docs/reference/parameters). The [MDN image format guide](https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Image_types) is included as supplementary browser guidance. This article contains no universal compression percentage because the result depends on the source, dimensions, quality, and encoder settings. --- Markdown path: https://keenpix.com/blog/best-image-cdns-2026.md # Best Image CDNs in 2026: 10 Services Compared > Compare 10 image CDN and optimization services using verified August 2026 pricing, use cases, tradeoffs, and clear best-fit recommendations for developers. Canonical HTML: [https://keenpix.com/blog/best-image-cdns-2026](https://keenpix.com/blog/best-image-cdns-2026) Author: Raed Bahri Published: 2026-07-10 Last reviewed: 2026-08-31 “Best image CDN” articles often omit the assumptions behind their rankings. This comparison starts with two disclosures. First, **full disclosure: Keenpix is our product.** It appears below as one entry among ten, in its own camp, with its weaknesses listed the same way we list everyone else's. If that disclosure makes you trust the rest of the list more, good — that is the point. Second, every price and feature claim below carries its verification date and links to vendor-owned pricing or documentation. Cloudinary and imgix were re-checked on **August 31, 2026**; the other entries retain their displayed review dates. Pricing in this space changes quickly, so use the linked primary sources before making a purchase. Our method is intentionally narrow: compare published self-service pricing, billing units, image-format support, product scope, and self-host availability. We did not run a cross-provider performance benchmark, test private enterprise contracts, or rank support quality. “Best for” labels are editorial fit guidance, not measured winners. ## The short version [#the-short-version] | Tool | Best for | Entry paid plan (August 2026) | Billing meter | Self-host? | | ---------------------------- | -------------------------------------------------------- | ------------------------------------------------------ | ----------------------------------------------- | ---------------- | | Cloudinary | Teams that need DAM + video + AI in one platform | $99/mo monthly; $89/mo annual equivalent (225 credits) | Pooled credits | No | | imgix | Teams needing a broad rendering API | $25/mo (100 credits) | Credit package with media + delivery allowances | No | | ImageKit | All-in-one media stack on a budget | $9/mo (40 GB) | Bandwidth, tiered | No | | Gumlet | Teams that want image and video products from one vendor | $32/mo (300 GB image bandwidth) | Bandwidth; $0.15/GB image overage | No | | TwicPics | EU teams wanting an EU vendor | $19/mo (40 GB) | Bandwidth | No | | Cloudflare Images | Small sites already all-in on Cloudflare | Pay-as-you-go ($0.50/1k transforms) | Transforms + storage + delivery counts | No | | Bunny Optimizer | Sites that want a flat optimizer fee plus CDN bandwidth | $9.50/mo per site + CDN bandwidth | Flat fee + bandwidth | No | | Vercel Image Optimization | Small Next.js apps on Vercel | Included in Pro, usage-billed | Transforms + cache reads + cache writes | No | | imgproxy | Maximum control, zero SaaS dependency | Free (Apache-2.0); Pro from $49/mo | None (self-host) | Yes (Apache-2.0) | | Keenpix *(our product)* | Low-cost managed image delivery with a self-host option | $9/mo (100 GB) | Optimized managed delivery | Yes (AGPL) | Now the details, grouped by camp — because "which image CDN is best" is really four different questions depending on which camp you are shopping in. Have a concrete workload? The [image CDN cost calculator](/image-cdn-cost-calculator) applies the same delivery, request, storage, transform, project, domain, and region inputs across ten public pricing models. Partial estimates remain labeled instead of being presented as complete bills. ## Camp 1: Premium platforms [#camp-1-premium-platforms] These are full media platforms: storage, DAM, video, AI features, and image delivery in one product. You pay platform prices for platform breadth. ### Cloudinary [#cloudinary] **Best for:** teams that need digital asset management, video, and AI features in one vendor and have the budget for it. Cloudinary documents a broad product surface: DAM workflows, video transcoding, AI auto-tagging, content-aware cropping, SDKs, and integrations. **Pricing (verified August 31, 2026):** the free tier includes 25 credits/month, where one credit is 1,000 transformations *or* 1 GB managed storage *or* 1 GB viewing bandwidth. Plus is $99/month for 225 credits, or $89/month equivalent with annual billing. Advanced is $249/month for 600 credits. Cloudinary's comparison page documents custom-domain availability by plan. Sources: [Cloudinary pricing](https://cloudinary.com/pricing), [plan/credit comparison](https://cloudinary.com/pricing/compare-plans), and [credit documentation](https://cloudinary.com/documentation/developer_onboarding_faq_credits). **The tradeoff:** transformations, storage, and bandwidth draw from one credit pool, so forecasting requires modeling all three. Cloudinary describes its self-service plan limits as soft limits and says it contacts customers about upgrading after they exceed them; exact handling can depend on the account. We do not characterize an over-limit account as automatically suspended. Use the source-dated [Keenpix vs Cloudinary](/compare/cloudinary-alternative) decision guide for a reproducible canary. Among the products reviewed here, Cloudinary documents the broadest combination of DAM, video, and image features; that breadth is the reason to evaluate it despite the multi-use credit meter. ### imgix [#imgix] **Best for:** design-driven teams that want a deep rendering API and are comfortable forecasting a credit package. imgix publishes a broad transformation API and extensive documentation. If rendering controls are central to your product, it deserves evaluation against your own image set. **Pricing (verified August 31, 2026):** Starter is $25/month for 100 credits, Basic $75 for 375, Midrange $150 for 830, Growth $300 for 1,875, and Growth Plus $500 for 3,570. The published Starter allowances are up to 50 GB of media and 100 GB of delivery; annual packages cost about 17% less. Unused credits do not roll over. Sources: [imgix pricing](https://www.imgix.com/pricing), [purchasing credits and costs](https://www.imgix.com/faq-categories/purchasing-credits-and-costs), and the [credit consumption guide](https://www.imgix.com/credit-consumption). **The tradeoff:** a credit package represents media, delivery, and feature-dependent transformation usage, so teams need all relevant dimensions to size a plan. Public imgix documentation says customers can upgrade when they need more credits; it does not support blanket claims about arbitrary renewal increases or mid-cycle blocking. The source-dated [Keenpix vs imgix](/compare/imgix-alternative) decision guide compares the published meters and gives a reproducible canary method. ## Camp 2: Mid-market image SaaS [#camp-2-mid-market-image-saas] Purpose-built image (and increasingly video) services with sane entry pricing. This is the most crowded camp. ### ImageKit [#imagekit] **Best for:** teams that want storage, DAM-lite, video, and image optimization in one affordable product. ImageKit combines transformations, a media library, video support, and a free tier. That can make it a practical all-in-one candidate when those features matter. **Pricing (verified August 22, 2026):** the free plan includes 20 GB bandwidth/month and 3 GB DAM storage. Lite is $9/month for 40 GB, then $0.50/GB. Pro is $89/month for 225 GB, then $0.45/GB. Source: [ImageKit plans](https://imagekit.io/plans/). **The tradeoff:** ImageKit's own plan page says media delivery stops when the free plan reaches its 20 GB monthly limit. Paid Lite and Pro continue with published $0.50/GB and $0.45/GB overage rates. See [Keenpix vs ImageKit](/blog/keenpix-vs-imagekit) for the closest head-to-head. ### Gumlet [#gumlet] **Best for:** video-heavy sites that also need image optimization from one vendor. Gumlet offers both video and image products. Its image usage analytics documentation describes bandwidth as the usage metric, while the current pricing page lists image plans separately from video plans. **Pricing (verified August 22, 2026):** Gumlet's image Free plan includes 30 GB/month. Image Growth is $32/month with 300 GB and a published $0.15/GB overage; Image Business is $199/month with 2,500 GB and a $0.08/GB overage. Source: [Gumlet pricing](https://www.gumlet.com/pricing/) and its [image usage analytics documentation](https://docs.gumlet.com/docs/usage-analytics-for-images). **The tradeoff:** Gumlet sells image and video products with separate plan dimensions. Confirm that a quoted plan covers the image workload you are comparing rather than applying video-plan pricing to image delivery. ### TwicPics [#twicpics] **Best for:** EU teams that want a European vendor for procurement or data-locality comfort. TwicPics is a French company with a capable real-time image API and a pixel-perfect responsive-images approach. **Pricing (verified August 22, 2026):** free tier of 3 GB/month. Business is $19/month for 40 GB, with $0.50/GB overage. Source: [TwicPics pricing](https://www.twicpics.com/pricing). **The catch:** the included bandwidth is small for the price compared to peers, and the $0.50/GB overage stings at scale. The EU-vendor angle is the strongest reason to pick it over the rest of this camp. ## Camp 3: Image optimization as a CDN/platform feature [#camp-3-image-optimization-as-a-cdnplatform-feature] If you already pay Cloudflare, Bunny, or Vercel, their built-in image features are one toggle away. Convenient — with meters worth reading twice. ### Cloudflare Images [#cloudflare-images] **Best for:** small sites already all-in on Cloudflare that want basic resizing without another vendor. **Pricing (verified August 22, 2026):** 5,000 unique transformations/month free, then $0.50 per 1,000 unique transformations; storage at $5 per 100k images; delivery at $1 per 100k images. Repeat requests for the same transform in a month count once, which keeps costs down for stable image sets. **The tradeoff:** transformation, storage, and delivery counts are separate published meters, so cost depends on the number of stored assets, delivered images, and unique variants. Model those dimensions from your own traffic rather than relying on unsupported reputation or rate-limit claims. Source: [Cloudflare Images pricing](https://developers.cloudflare.com/images/pricing/). Worth being clear about our own position here: Keenpix is *not* a Cloudflare replacement. It is designed to sit **behind** Cloudflare (or any CDN) as the transformation origin. A customer-owned CDN hit that never reaches Keenpix is not Keenpix usage; delivery through Keenpix's own managed Cloudflare edge is counted once. Your CDN's current plan and charges still apply. Complementary layers, not rivals. ### Bunny Optimizer [#bunny-optimizer] **Best for:** a site that wants unlimited optimizer operations for a flat per-site fee, with CDN bandwidth billed separately. Bunny's published model is simple to calculate: a flat optimizer fee per site plus separately billed CDN bandwidth. **Pricing (verified August 22, 2026):** $9.50/month per website for unlimited optimizer requests, optimizations, and transformations; CDN bandwidth is billed separately and varies by network/region. Sources: [Bunny Optimizer pricing](https://docs.bunny.net/optimizer/pricing) and [Bunny CDN pricing](https://bunny.net/pricing/cdn/). For some single-site traffic profiles this combination can price below Keenpix, but that is workload- and region-dependent rather than a universal cheapest claim. **The tradeoff:** Bunny added AVIF input/output in April 2026 and promoted it to general availability on June 26, with a four-megapixel AVIF output limit and fallback for larger images. The service has no self-host path, and the optimizer fee applies per website. Sources: [Bunny Optimizer formats](https://docs.bunny.net/optimizer/dynamic-images/formats) and [Bunny changelog](https://docs.bunny.net/changelog). ### Vercel Image Optimization [#vercel-image-optimization] **Best for:** small Next.js apps already deployed on Vercel, where `next/image` works out of the box. For an existing Vercel-hosted Next.js app, the built-in integration can reduce setup work and supports automatic formats. **Pricing (verified August 22, 2026):** Hobby includes 5,000 transformations, 300k cache-read units, and 100k cache-write units per month. Vercel says new Hobby transformations return a 402 after a limit is exceeded while previously cached images continue working. On-demand regional rates are $0.05–0.0812 per 1,000 transformations, $0.40–0.64 per million cache-read units, and $4.00–6.40 per million cache-write units, plus Fast Data Transfer and Edge Requests. Source: [Vercel image optimization limits and pricing](https://vercel.com/docs/image-optimization/limits-and-pricing). **The tradeoff:** that is *three* image-optimization meters plus data transfer. Forecast with Vercel's current usage model and your actual cache behavior; we do not rely on anecdotal crawler-billing claims here. ## Camp 4: Open source [#camp-4-open-source] The camp where you stop renting and start owning. ### imgproxy [#imgproxy] **Best for:** teams that want a battle-tested, standalone transform server and are happy to bring their own cache, CDN, and monitoring. imgproxy is a mature open-source image processor with an Apache-2.0-licensed core. Pro is a separate commercial edition. Source: [imgproxy source and license](https://github.com/imgproxy/imgproxy) and [imgproxy Pro pricing](https://imgproxy.net/pricing/). **Pricing (verified August 22, 2026):** the open-source version is free and includes more than people assume — basic watermarks and basic smart crop are in the free core. Pro is $49/month (or $499/year) per 16 workers and adds the advanced tier: object-detection cropping, dynamic watermarks, autoquality, automatic best-format selection, and video/PDF thumbnails. **The catch:** imgproxy is a transform engine, deliberately nothing more. No dashboard, no analytics, no built-in cache layer, no managed cloud option. You assemble the rest yourself — which is either the whole appeal or a dealbreaker, depending on your team. ### Keenpix — *disclosure: this is our product* [#keenpix--disclosure-this-is-our-product] **Best for:** developers who want unlimited transforms and teammates billed on optimized managed delivery, with a self-host escape hatch. Keenpix is an image optimization and delivery layer: point a URL like `/img/?w=800&fmt=auto` at images on origins you already have (S3, R2, your own servers), and it handles documented resize, crop, quality, AVIF/WebP negotiation, and image modifiers with disk+memory caching and stale-while-revalidate. Security is allowlist-based — per-project origin-host allowlists and SSRF hardening instead of public API keys, with optional HMAC-signed URLs. Analytics (managed delivery, bandwidth saved, cache hit rate, format mix, latency percentiles, live request logs) are included on every tier, not gated to enterprise. **Pricing (verified from the v0.3.1 source catalog on August 31, 2026):** the standard catalog lists Basic at $9/month for 100 GB of optimized managed delivery, Pro at $29 for 400 GB, and Business at $69 for 1 TB. Standard overage is $0.12/$0.09/$0.07 per delivered GB. A limited founding offer may display lower Pro, Business, and overage prices while places remain; the live pricing page is authoritative. Managed Cloudflare edge hits, Keenpix cache hits, and new origin transforms each count once; optimizer savings remain separate analytics, while transformations and team members are unlimited. [Read the exact meter boundary](/blog/transparent-image-cdn-pricing) or [see current plan details](/pricing). Paid delivery continues and Polar bills accumulated overage at the end of the billing period. The engine is available under AGPL-3.0 for [self-hosting](/docs/self-hosting). Keenpix does not currently offer AI image operations. **The honest cons, same standard as everyone above:** there is no video support, DAM, storage, or upload library — Keenpix transforms and returns images from origins you already have. It is a younger product than the established platforms above, and public customer evidence is still limited. Managed custom delivery domains require Pro or Business. Depending on region and traffic shape, Bunny's flat optimizer fee plus CDN bandwidth can cost less. If those constraints matter, evaluate the other camps above. ## Honorable mentions [#honorable-mentions] * **Sirv** — free 2 GB transfer/month, Business $19/month for 20 GB. The pick if you need e-commerce specifics like 360° spin and deep zoom viewers. * **Uploadcare** — free 5 GB; Pro $66/month for 25 GB plus 5k operations, $0.35/GB after. Built around user-upload pipelines (validation, virus scanning, moderation) more than delivery. ## How to choose [#how-to-choose] Match the tool to the actual shape of your problem: * **You need DAM, video, and AI in one vendor:** Cloudinary if budget allows, ImageKit if it does not. * **Rendering controls are central to the product:** evaluate imgix's API against your own image set and size the credit package. * **You want one affordable vendor for storage + images + video:** ImageKit. * **Video is the main event:** Gumlet (or Cloudinary at the premium end). * **You want an EU vendor:** TwicPics. * **You are small and already on Cloudflare:** Cloudflare Images is fine; add a real transform layer behind it when you outgrow the analytics. * **One site and a flat optimizer fee:** Bunny Optimizer, which now supports AVIF; add region-specific CDN bandwidth to the comparison. * **Small Next.js app on Vercel:** the built-in optimizer, until the three meters start mattering. * **Full self-host control, no dashboard needed:** imgproxy. * **Transform-and-deliver from origins you already have, one managed-delivery meter, self-host option:** Keenpix — with the caveats we listed against ourselves above. Still torn between running it yourself and paying someone? We wrote a framework for that too: [self-hosted vs managed image optimization](/blog/self-host-vs-managed-image-optimization). *Latest targeted verification: August 31, 2026 for Cloudinary, imgix, and the Keenpix source catalog. Other entries retain the dates shown beside them. Sources are linked beside the high-risk pricing and feature claims. Prices exclude taxes, negotiated enterprise terms, and workload-specific transfer effects. If a vendor changes a source, the current vendor page takes precedence over this article.* --- Markdown path: https://keenpix.com/blog/bring-your-own-origin-image-cdn-architecture.md # Bring-your-own-origin image CDN architecture > Design an image delivery layer around source assets you already own, with explicit origin, cache, URL, security, and recovery boundaries. Canonical HTML: [https://keenpix.com/blog/bring-your-own-origin-image-cdn-architecture](https://keenpix.com/blog/bring-your-own-origin-image-cdn-architecture) Author: Raed Bahri Published: 2026-09-02 Last reviewed: 2026-09-02 A bring-your-own-origin image CDN leaves master assets in storage you control and puts a transformation and delivery layer in front of them. The application requests a derived URL, the delivery layer validates and fetches the source, transforms it when necessary, caches the result, and returns the optimized bytes. This model is a good fit when source ownership and reversibility matter. It is a poor fit when you actually need a digital asset manager, upload workflow, video pipeline, or editorial media library. ## The five boundaries to decide first [#the-five-boundaries-to-decide-first] | Boundary | Decision | Failure if left implicit | | ------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------ | | Source of truth | Which bucket, host, or application owns the original? | Two systems can both appear authoritative after a migration. | | Read access | Can the transformer read public objects, signed URLs, or a private network endpoint? | Cold transforms fail while cached images appear healthy. | | URL identity | Which path and parameters identify one output variant? | Cache collisions or unbounded variant growth. | | Delivery ownership | Who terminates TLS, caches responses, purges, and absorbs traffic spikes? | An origin or transformer becomes the accidental edge. | | Reversal path | How does the application return to the old delivery URL? | A failed rollout becomes an emergency rewrite. | ### Prerequisites [#prerequisites] Before changing traffic, collect: 1. A representative source set: photographs, transparent graphics, screenshots with text, animated images, and the largest dimensions you accept. 2. Every current image URL pattern, including framework loaders, CSS backgrounds, social images, emails, feeds, and stored rich text. 3. The origin's authentication model, redirect behavior, cache headers, MIME types, and maximum object size. 4. The layout slots and device widths that determine a finite responsive width ladder. 5. A rollback switch that does not depend on the new service being healthy. ## Reference request path [#reference-request-path] ```text Browser -> application markup chooses a responsive candidate -> delivery hostname receives source identity + transform parameters -> request policy verifies host, signature, dimensions, and format -> cache looks up the complete output identity -> transformer fetches the allowlisted source on a miss -> decoder enforces byte, pixel, timeout, and redirect limits -> encoder creates one output -> cache stores the result -> delivery layer returns Content-Type, Cache-Control, ETag, and Vary ``` Keep control-plane operations—creating projects, changing allowlists, rotating keys, or purging caches—separate from image delivery. A browser should not need an administrative API key to render an image. If a delivery URL must be restricted, use a purpose-built signature or application authorization layer whose expiry and cache behavior you have tested. ## Choose the source access model deliberately [#choose-the-source-access-model-deliberately] ### Public, unguessable, and private are different [#public-unguessable-and-private-are-different] A public object can be fetched by anyone who knows its URL. An unguessable path reduces accidental discovery but is not authorization. A private object requires a credential, network boundary, or short-lived grant. | Model | Works well when | Main risk | | --------------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | | Public origin + strict host allowlist | Assets are intentionally public and the CDN URL is the main delivery path. | The source URL can still bypass the delivery layer unless the origin has another control. | | Origin allows only the delivery service | The provider offers stable egress identity or an authenticated origin request. | Rotation and regional expansion can break the allow rule. | | Short-lived presigned source URL | The source grant will remain valid through cold transform and retry windows. | Expiry becomes part of the cache key or fails later revalidation. | | Private network or service binding | Transformer and origin share controlled infrastructure. | Tighter operational coupling and harder managed-service portability. | Read the [private-origin security guide](/blog/private-image-origins-security-boundaries) before treating a signed URL or secret header as a complete architecture. ## Define a finite output contract [#define-a-finite-output-contract] Start from rendered slots, not arbitrary numbers. For example, a 360 CSS-pixel card shown on 1× and 2× screens may need 360 and 720 pixel candidates. Add larger breakpoints only where the layout uses them. ```html Product dashboard showing image delivery analytics ``` The cache identity must include every input that can change the bytes: normalized source, width, height, fit, crop position, quality, output format, and any watermark or version. If `fmt=auto` varies by the request `Accept` header, the delivery and every intermediate cache must either honor `Vary: Accept` or normalize supported formats into distinct cache keys. The [cache-key guide](/blog/image-cdn-cache-keys-vary-accept) covers the trade-off. ## Roll out from observation to traffic [#roll-out-from-observation-to-traffic] 1. **Inventory:** record current URLs, source hosts, formats, cache headers, and error behavior. 2. **Shadow:** generate candidate delivery URLs without putting them in user markup. Compare status, dimensions, visual output, MIME type, and bytes. 3. **Canary:** switch a small, identifiable route or asset class. Keep the old URL generator deployable. 4. **Measure:** compare cold and warm behavior, origin fetches, cache-hit ratio, browser-selected candidates, layout stability, and errors. 5. **Expand:** move one asset class or application surface at a time. 6. **Retire carefully:** wait through the longest relevant cache and content lifetime before removing the old path. Do not call a rollout successful because one transformed URL returned 200. Use the [reproducible measurement guide](/blog/reproducible-image-performance-measurement) and the [safe rollout and rollback guide](/blog/safe-image-cdn-rollouts-and-rollbacks). ## Failure modes and what they usually mean [#failure-modes-and-what-they-usually-mean] * **Warm images work; new images fail:** the transformer cannot fetch the origin, the grant expired, or a redirect target is not allowed. * **Wrong crop at one breakpoint:** the width ladder or `sizes` attribute does not match the rendered slot. * **AVIF reaches an unsupported client:** an intermediary ignored `Vary`, or format negotiation was cached under an incomplete key. * **Origin traffic rises after the move:** the delivery cache is bypassed, fragmented, or expiring too quickly. * **One source path works but another returns 403:** object policy, case sensitivity, encoding, or redirect behavior differs. * **Bills grow without traffic growth:** variant cardinality, duplicated delivery layers, request pricing, or transform pricing changed the unit economics. ## Verification checklist [#verification-checklist] * Inspect the browser's chosen `currentSrc`, decoded dimensions, and response `Content-Type`. * Repeat the same request and confirm the intended cache path, not just a lower duration. * Request unsupported dimensions and formats; confirm bounded rejection or normalization. * Test redirects to an allowed host, a disallowed host, loopback, and a private IP. * Remove origin access temporarily in a non-production environment and observe cold versus warm behavior. * Exercise the rollback switch while the new delivery hostname is unavailable. * Record the exact application revision, configuration, sample set, region, time, and acceptance thresholds. ## Using Keenpix for this architecture [#using-keenpix-for-this-architecture] Keenpix reads images from allowlisted HTTP(S) origins and exposes transform parameters through delivery URLs. Managed cloud provides the operated delivery path; self-hosting gives you the engine and makes CDN, network policy, upgrades, backups, and incident response your responsibility. Start with the [cloud quickstart](/docs/getting-started/cloud-quickstart) or [self-hosting architecture](/docs/self-hosting), then verify the [endpoint contract](/docs/reference/endpoint). Keenpix is not the right fit if you need a DAM, upload library, video transformations, arbitrary authenticated origin headers, or a vendor-managed private network connection that the current product does not provide. Keep those paths with a platform that supports them, or add an origin gateway you can secure and operate. ## Sources and limitations [#sources-and-limitations] * [HTTP caching, MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching), reviewed September 2, 2026. * [Responsive images, MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Responsive_images), reviewed September 2, 2026. * [Fetch Metadata Request Headers, W3C](https://www.w3.org/TR/fetch-metadata/), reviewed September 2, 2026. * [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html), reviewed September 2, 2026. This is an architecture guide, not a measured performance or security result. Validate the chosen provider, origin, CDN, framework, and region with your own representative assets. ## Related guides [#related-guides] * [What is an image CDN?](/blog/what-is-an-image-cdn) * [Responsive image CDN implementation](/blog/responsive-image-cdn-guide) * [Symptom-first image delivery troubleshooting](/blog/image-delivery-troubleshooting-by-symptom) --- Markdown path: https://keenpix.com/blog/cache-invalidation-versioned-image-urls.md # Cache invalidation with versioned image URLs > Use immutable versioned image URLs across browser, CDN, and origin caches, with safe publication, rollback, purge, revocation, and verification patterns. Canonical HTML: [https://keenpix.com/blog/cache-invalidation-versioned-image-urls](https://keenpix.com/blog/cache-invalidation-versioned-image-urls) Author: Raed Bahri Published: 2026-09-03 Last reviewed: 2026-09-03 The dependable way to update a public image is to publish new bytes at a new URL, update the application reference, and keep the previous version available through a measured rollback window. Use long-lived immutable caching only for URLs whose bytes will never change. Treat purge as an operational accelerator or emergency tool, not as proof that every browser, proxy, CDN layer, and downstream consumer forgot the old response. This distinction resolves most cache-invalidation confusion: **versioning changes identity; purging tries to remove stored state for an existing identity**. Identity is under your application’s control. Complete invalidation across independent caches usually is not. ## Model every cache in the path [#model-every-cache-in-the-path] A browser request rarely travels through one cache: ```text HTML or API asset reference -> browser memory cache -> browser disk cache / service worker -> customer CDN or reverse proxy -> image delivery edge -> regional shield / optimized-variant cache -> transformer -> source object store or origin cache ``` Each layer can use a different key, freshness lifetime, revalidation rule, and purge mechanism. A successful CDN purge says nothing about a browser disk entry. Purging the image-delivery edge may leave a customer-owned CDN in front of it warm. Replacing an object at the origin may leave already generated derivatives valid under their old cache keys. [RFC 9111](https://www.rfc-editor.org/rfc/rfc9111.html) defines HTTP cache behavior, including freshness, validation, and invalidation after unsafe requests. Its invalidation rules apply to caches through which a request travels; they are not a global recall protocol. That is why a design that requires every cache to delete the same public URL before correctness returns is fragile. ## Separate source identity from transform identity [#separate-source-identity-from-transform-identity] An output variant needs both a source version and a transform specification. For example: ```text https://images.example.com/project_7/assets/hero/v_18/source.jpg?w=1280&q=76&fmt=avif ``` The source identity is `project_7/assets/hero/v_18/source.jpg`. The transform identity includes normalized width, quality, format, crop, fit, and every option that changes bytes. If `fmt=auto` negotiates from `Accept`, every cache must honor the response’s `Vary` behavior or the delivery layer must normalize the chosen format into a distinct internal key. See the [cache-key and Vary guide](/blog/image-cdn-cache-keys-vary-accept). Do not confuse these identities: * `hero.jpg?v=18` is versioned only if every relevant cache includes the query in its key and the origin maps it to immutable version 18. * `/hero/v_18.jpg` is not immutable if an operator can overwrite its bytes. * an object-store version ID protects stored history but does not automatically appear in the public URL. * an ETag validates a representation; it does not create a new public identity. * a transform cache key without the source version can keep returning a derivative of replaced bytes. Use a URL form your entire path preserves. A version segment or content digest in the path is usually easier to inspect than a query parameter, but either can work when cache and routing behavior are verified. ## Choose a versioning strategy [#choose-a-versioning-strategy] ### Application version IDs [#application-version-ids] Generate an opaque version ID when an asset passes publication gates: ```text /assets/{assetId}/{versionId}/source.jpg ``` This works well when metadata, moderation, and the source object move through a managed lifecycle. Store the version as data, not a timestamp guessed from a client filename. The database record can point to the current version while old references remain resolvable during rollback. ### Content digests [#content-digests] Hash the approved source bytes and include the digest, or a collision-resistant prefix, in the path: ```text /assets/{assetId}/sha256-7e3c.../source.jpg ``` Content addressing makes identical bytes share identity and makes accidental mutation detectable. It does not express policy changes that leave source bytes unchanged. If cropping rules, color management, watermark policy, or transformation code changes the output, include a processing-policy version in the derivative identity as well. ### Build fingerprints [#build-fingerprints] For repository-owned images, a bundler-generated content hash is often sufficient: ```text /assets/hero.4f8ca91c.webp ``` The HTML deployment references the new hash. Keep old assets available while old HTML, client-side route caches, emails, and rollbacks may still reference them. Do not delete the previous build’s assets immediately after switching traffic. ### Object-store versions [#object-store-versions] Versioned storage is valuable recovery protection. Amazon S3 assigns a version ID to stored object versions and retains versions as full objects, according to its [versioning workflow documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/versioning-workflows.html). Still decide how the application maps a public versioned URL to that storage version. A mutable public `/hero.jpg` that happens to live in a versioned bucket remains a mutable cache identity. ## Publish in the right order [#publish-in-the-right-order] The safe sequence is additive: 1. Write new source bytes under a never-before-used immutable identity. 2. Verify the object is readable from the origin path the transformer will use. 3. Generate mandatory derivatives or perform representative cold transforms. 4. Validate content type, dimensions, cache headers, and bytes from the actual delivery host. 5. Update the application’s current-version pointer or deploy markup referencing the new URL. 6. Observe errors and rendered pages while the previous version remains available. 7. Retire old versions only after the rollback and long-tail reference windows close. Changing the pointer last prevents the application from advertising an object that has not propagated or cannot be transformed. Keeping the old version prevents a rollback from depending on reconstructing deleted bytes. For a user-upload system, record the source object and `currentVersionId` in a transaction with an outbox event. The [end-to-end upload pipeline guide](/blog/user-upload-image-pipeline-design) covers quarantine, validation, moderation, and atomic publication before delivery. ## Set cache headers by mutability [#set-cache-headers-by-mutability] For a truly immutable, public versioned URL, a long freshness lifetime is appropriate: ```http Cache-Control: public, max-age=31536000, immutable ETag: "sha256-7e3c..." ``` The `immutable` directive tells supporting clients that a fresh response does not need revalidation. MDN’s [HTTP caching guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching) recommends cache busting with a version or hash in the URL for long-lived static resources and explains why deleting stored responses everywhere is difficult. For a mutable pointer such as `/assets/hero/current`, use a shorter freshness period and validators: ```http Cache-Control: public, max-age=60, stale-while-revalidate=300 ETag: "asset-hero-current-v18" ``` The pointer may redirect to or return metadata containing the immutable URL. Keep mutable pointers out of long-lived HTML when direct versioned URLs are practical. If a service worker caches images, version and test its cache namespace too; a service worker can return an old response without contacting the network. Private or user-specific images need a different policy. Do not mark a response public merely because its path is versioned. Viewer authorization, shared-cache behavior, signed-URL expiry, and revocation must be designed together. Read the [private-origin security guide](/blog/private-image-origins-security-boundaries) and [signed URL guide](/blog/signed-image-urls-hmac) before caching protected media. ## What a purge can and cannot do [#what-a-purge-can-and-cannot-do] A purge is useful when: * a mutable response received the wrong cache lifetime; * corrupted derivatives exist under a valid key; * transformation policy produced unsafe output; * legal or security response requires best-effort removal; * capacity makes waiting for freshness unacceptable. Purge the exact layers you control and record their results. Provider acceptance of a purge request is not the same as a verified miss at every edge. Test a representative location after completion and inspect `Age`, cache-status headers, ETag, content type, and a digest of the body. A purge cannot reliably recall bytes already stored in browser caches, downloaded files, screenshots, feeds, email proxies, search caches, or third-party archives. For material that must be revocable, do not expose a permanently public immutable URL. Require authorization on each sufficiently fresh request, keep lifetimes bounded, and accept the availability and cache-hit trade-off. ## Overwrites create ambiguous incidents [#overwrites-create-ambiguous-incidents] Suppose `/hero.jpg` is cached for one year and an operator overwrites the origin object. You now have at least two byte sequences with one identity. Some viewers see the old response, some see the new response, and a derivative cache may contain mixtures created before and after the overwrite. Revalidation can reduce the inconsistency but cannot make the URL’s historical meaning unambiguous. With `/hero/v17.jpg` and `/hero/v18.jpg`, each URL has one meaning. The application pointer says which version is current. Rollback changes the pointer to v17. Diagnostics can compare exact versions without guessing which bytes an edge held at a given time. Never reuse a failed version identifier. If v18 was published incorrectly, publish corrected bytes as v19. Reusing v18 after a purge recreates the same ambiguity and makes incident evidence harder to interpret. ## Coordinate HTML, API, and image caches [#coordinate-html-api-and-image-caches] Versioned images solve only the media object. The HTML or API response containing the image URL also has a cache lifetime. A new image version will not appear until consumers receive the new reference. Map the complete update path: | Layer | Identity to change or refresh | Verification | | ------------------ | ---------------------------------------- | -------------------------------------- | | Database | `currentVersionId` or equivalent pointer | Read after write from the serving path | | API response | Resource representation or tag | Response body and validator | | HTML / RSC payload | Page or route cache | Rendered markup uses new URL | | Service worker | Cache namespace and fetch policy | Clean and upgrade scenarios | | Image CDN | Source version plus transform key | Cold and warm response headers/body | | Browser | New URL in DOM | Network panel and rendered pixels | If HTML caches for ten minutes, an image update can legitimately take ten minutes to become referenced even though the new version is available immediately. Either accept that window, revalidate the HTML/API layer, or send an application event that causes clients to refresh. Do not purge millions of image variants when the stale layer is actually the page that still points to the old URL. ## Plan rollback before cleanup [#plan-rollback-before-cleanup] A versioned rollout makes rollback a pointer change, but only while old dependencies still exist. Record: * the previous application revision and asset version; * the source and derivative retention window; * database schema compatibility; * which HTML, API, CDN, and service-worker caches may hold each reference; * the condition that triggers reversal; * an owner and maximum decision time. During rollback, restore the old reference, verify it from the delivery host, then render affected desktop and mobile pages. A green deployment or successful raw HTTP response does not prove the browser selected and displayed the intended candidate. The [safe rollout guide](/blog/safe-image-cdn-rollouts-and-rollbacks) provides a broader canary and reversal method. ## Keenpix cache behavior and the natural integration point [#keenpix-cache-behavior-and-the-natural-integration-point] Keenpix derives and caches variants from a source URL and transformation parameters. Give the source a new versioned URL when its bytes change. Then the resulting transformed URL also changes, which avoids depending on an all-layer purge for correctness. Keep width, height, fit, position, quality, format, and other byte-changing inputs bounded and explicit. If automatic AVIF or WebP negotiation is enabled, verify `Vary: Accept` and cache-key behavior through any CDN you place in front. The [AVIF and WebP production guide](/blog/avif-vs-webp-production-caching) covers explicit-format tests and failure handling. The [capacity guide](/blog/image-transform-cache-stampedes-capacity) explains why a burst of entirely new versioned variants is different from warm-cache traffic. Keenpix does not control a browser cache or a customer-owned CDN placed in front of it. It also cannot determine when your application has safely completed moderation or publication. Those remain application and infrastructure responsibilities. On managed Keenpix, a browser cache hit or customer-owned front-cache hit does not reach the managed delivery network; the [pricing guide](/blog/transparent-image-cdn-pricing) documents the billing boundary. ## Verification matrix [#verification-matrix] Run a small matrix for every critical image path: 1. Request the old version cold and warm; record status, content type, cache status, `Age`, ETag, and body digest. 2. Publish the new source under a new URL without changing the application reference; prove the old page still works. 3. Request representative new AVIF, WebP, and fallback outputs cold, then warm. 4. Change the application pointer and verify the raw HTML or API reference. 5. Render the page at desktop and mobile widths; confirm the selected `currentSrc`, dimensions, crop, and pixels. 6. Restore the old pointer and repeat the rendered check. 7. If purge is part of the runbook, test it separately and name the exact cache layers it covers. Include a query-string case if versions use queries, a customer-CDN case if one exists, and a service-worker upgrade case if the application registers one. Test stale clients that keep old HTML open across the publication event. Use the [symptom-based troubleshooting guide](/blog/image-delivery-troubleshooting-by-symptom) when a layer disagrees, and use [reproducible image performance measurement](/blog/reproducible-image-performance-measurement) to keep cold-transform latency separate from warm-cache delivery and browser rendering. ## The durable rule [#the-durable-rule] Cache invalidation becomes manageable when correctness does not depend on deletion. Publish immutable bytes at a unique URL, switch references only after verification, and retain the previous version long enough to reverse. Use bounded freshness and validators for mutable pointers. Reserve purges for acceleration and incidents, and describe their coverage honestly. That model gives browsers and CDNs permission to cache aggressively without making asset replacement mysterious. It also produces better evidence: a URL names one byte sequence, a pointer records the current choice, and a rollback restores a known version instead of hoping every cache has forgotten an overwrite. --- Markdown path: https://keenpix.com/blog/image-cdn-cache-keys-vary-accept.md # Image CDN cache keys: tame Vary: Accept > Design image cache keys that keep AVIF, WebP, and JPEG correct without multiplying variants for every raw Accept header or query permutation. Canonical HTML: [https://keenpix.com/blog/image-cdn-cache-keys-vary-accept](https://keenpix.com/blog/image-cdn-cache-keys-vary-accept) Author: Raed Bahri Published: 2026-08-23 Last reviewed: 2026-08-23 An image CDN cache key should identify the bytes that can change, not every spelling of the request that produced them. Put the source identity and effective transform parameters in the key. When one URL negotiates AVIF, WebP, or JPEG, add one normalized output-format dimension—or use an explicit `fmt` value in the URL. Do not blindly key on the entire raw `Accept` string. That answer has two parts because there are two caches. Keenpix builds an internal transform key after it has resolved the output format and Client Hints. A CDN in front of Keenpix sees the public URL and request headers. Both layers must agree about representation identity, but they do not have to serialize the key the same way. ## The minimum safe identity [#the-minimum-safe-identity] For a deterministic image transform, start with this model: ```text source identity + tenant or project boundary + effective width, height, DPR, fit, crop, quality, format, and effects + configuration that changes pixels, such as a watermark = one reusable representation ``` The request method and target URI are the minimum HTTP cache key under [RFC 9111](https://www.rfc-editor.org/rfc/rfc9111.html#section-2). A stored response carrying `Vary: Accept` may be reused only when the selecting request field matches as defined by the RFC. `Vary` protects correctness; it does not promise that a particular CDN will store the response, normalize equivalent values, or keep the number of variants small. | Key choice | Correctness | Cardinality risk | Operational use | | ------------------------------------------------------ | ------------------------------------------------------- | -------------------------------------------------------------- | ----------------------------------------------------- | | Full path and normalized query + explicit `fmt` | Strong and easy to inspect | Bounded by the URL variants you emit | Preferred when the CDN cannot prove image negotiation | | Full path and query + normalized AVIF/WebP/JPEG bucket | Strong if edge and origin use the same negotiation rule | Three format buckets per transform | Useful at a programmable edge | | Full path and query + raw `Accept` | Usually correct when `Vary` is honored | Many syntactically different headers can select the same bytes | Diagnose before accepting the storage cost | | URL only while origin uses `fmt=auto` | Unsafe | Looks efficient because variants collide | Can serve AVIF bytes to a browser that needs JPEG | | URL with query ignored | Unsafe for transforms | Widths, qualities, crops, and signatures collide | Never use for `/img/*` | ## Observation: what Keenpix keys today [#observation-what-keenpix-keys-today] Verified against the public repository on August 23, 2026: Keenpix parses the request first, resolves `fmt=auto` to `avif`, `webp`, or `jpeg`, rounds automatic Client Hints to bounded ladders, then hashes this object with SHA-256: ```text projectId + source URL + full effective transform options ``` The effective options include the resolved output format, dimensions, DPR, quality, crop and effects, metadata policy, and project watermark configuration. A signature is checked before the internal cache lookup but is not part of this transform key. Two valid signed URLs for the same effective transform can therefore share internal bytes. The current automatic ladders contain 12 widths (`320` through `5000`) and seven DPR values (`1` through `3`). Before other options, that creates a theoretical ceiling of 252 header-derived width/DPR/format combinations for one source: `12 × 7 × 3`. A project maximum width and the browser population usually reduce that set. The number is a code-derived upper-bound model, not a measured Keenpix production count. Every successful transform response currently sends: ```http Cache-Control: public, max-age=31536000, immutable Accept-CH: Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width Vary: Accept, Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width, DPR, Width, Viewport-Width ``` Those headers describe every request field that can affect the response. They do not configure your outer CDN for you. Read the [Keenpix caching reference](/docs/concepts/caching) and [parameter bounds](/docs/reference/parameters) before changing the edge key. ## Why raw Accept can multiply storage [#why-raw-accept-can-multiply-storage] These request headers all select AVIF in the current Keenpix parser: ```http Accept: image/avif,image/webp,image/* Accept: image/avif,image/webp,*/*;q=0.8 Accept: image/webp,image/avif,image/png,*/* ``` Keenpix collapses them internally because it stores the resolved `avif` format in the transform options. An intermediary that retains a separate object for each raw header value may store three copies of identical bytes. Browser versions, embedded webviews, crawlers, and application fetches create more spellings over time. There is another important limit: the current Keenpix negotiation code checks whether the header contains `image/avif`, then `image/webp`; it does not rank media-type quality values. A custom edge normalizer for Keenpix must mirror that exact rule until the origin parser changes. A general-purpose media server that honors `q=0` or quality ordering needs a real `Accept` parser instead. Never deploy a cache normalizer whose selection logic differs from the origin. ## Recommendation: make the format explicit when possible [#recommendation-make-the-format-explicit-when-possible] An explicit URL is the easiest cache contract to operate: ```html Product team reviewing an image delivery dashboard ``` The browser chooses a supported ``, and each resulting URL has one format. Keep the width ladder finite and generated from actual layout slots. Do not create an arbitrary integer width endpoint merely because the transform service accepts one. Use `fmt=auto` when a single URL matters and the CDN has documented, tested image-variant support. Cloudflare's official Vary for Images feature parses `Accept` into image variants, but it requires a configured variants rule and a compatible image extension in the path. Cloudflare also documents that custom request-header cache keys are Enterprise-only. Returning `Vary: Accept` by itself is not equivalent to enabling either feature. ## A bounded Nginx cache example [#a-bounded-nginx-cache-example] The safest Nginx example uses explicit `fmt` URLs, so no request-header normalization is needed: ```nginx proxy_cache_path /var/cache/nginx/keenpix levels=1:2 keys_zone=keenpix:20m max_size=20g inactive=30d use_temp_path=off; server { listen 443 ssl; server_name images.example.com; location /img/ { proxy_pass http://keenpix_transform; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache keenpix; proxy_cache_methods GET HEAD; proxy_cache_convert_head on; proxy_cache_key "$scheme$host$request_uri"; proxy_cache_valid 200 30d; # Fallback when the origin sends no cache lifetime. add_header X-Cache-Status $upstream_cache_status always; } } ``` Nginx enables `proxy_cache_convert_head` by default, converting `HEAD` to `GET` for caching. Omitting the request method from this key deliberately lets `HEAD` reuse the `GET` representation. If you disable that conversion, follow the official directive guidance and include `$request_method` so responses from different methods cannot collide. `$request_uri` retains the complete path and query. Do not switch to `$uri` alone, do not enable an ignore-query-string mode, and do not exclude `sig` from an outer key unless the edge verifies that signature before cache lookup. The [signed URL guide](/docs/concepts/signed-urls) explains why an unverified cache hit can bypass origin enforcement. `proxy_cache_valid 200 30d` is only a fallback lifetime. Nginx gives origin `X-Accel-Expires` or, when it is absent, `Expires` and `Cache-Control` response headers higher priority than that directive. Keenpix's current `Cache-Control: public, max-age=31536000, immutable` therefore takes precedence unless an operator explicitly disables header processing. Do not add `proxy_ignore_headers Cache-Control Expires Vary` to this example. Nginx 1.7.7 and later do not cache `Vary: *`; for other `Vary` values, Nginx caches the response while taking the named request fields into account. This example deliberately retains that behavior. Even an explicit `fmt` URL can retain conservative header variants because Keenpix currently advertises `Vary` on every successful transform. Measure that cardinality before overriding it, and never ignore `Vary` unless every named header has been proven unable to change the bytes. For `fmt=auto`, verify cold and warm requests for AVIF, WebP, and fallback clients on the exact Nginx version and configuration you operate. If you instead add an explicit format bucket to `proxy_cache_key`, make the bucket logic identical to the origin and test fallback requests with no `Accept` header. A copied regex that looks only for `webp` can collapse AVIF and WebP into the wrong entry. ## Canonicalize what is semantically unordered [#canonicalize-what-is-semantically-unordered] Query order can fragment an outer cache even when the application parser treats these URLs identically: ```text ?project=store&w=1200&q=80&fmt=webp ?fmt=webp&q=80&w=1200&project=store ``` Generate one canonical order in your application. If the CDN offers query sorting, enable it only after confirming that no endpoint under the rule assigns meaning to duplicate-parameter order. Cloudflare documents Query String Sort as a way to increase hit rate, but the cache rule must remain scoped to the image endpoint. Do not remove “unknown” parameters at the CDN to improve hit rate when URLs are signed. Keenpix signs every query parameter except `sig`; changing the public query after signing makes the request invalid. Prefer a strict URL builder that never emits tracking or cache-buster parameters on transform URLs. ## Production verification matrix [#production-verification-matrix] Use one source and one transform URL. Purge that object or use a new versioned source path before the test. Then run each row twice from the same edge region: | Request | Expected MIME type | Expected second-request evidence | | --------------------------------------- | ------------------ | ------------------------------------------------- | | `Accept: image/avif,image/webp,image/*` | `image/avif` | Outer cache hit for AVIF bucket | | `Accept: image/webp,image/*` | `image/webp` | Outer cache hit for WebP bucket | | `Accept: image/jpeg,image/*` | `image/jpeg` | Outer cache hit for fallback bucket | | Explicit `fmt=webp`, any `Accept` | `image/webp` | Same URL remains WebP | | Same params in a different query order | Same bytes | Same hit only if sorting is intentionally enabled | | Change `w=1200` to `w=640` | New dimensions | Separate object | Example probe: ```bash curl --fail --silent --show-error \ --dump-header - \ --output /dev/null \ --header 'Accept: image/webp,image/*' \ 'https://images.example.com/img/https://assets.example.com/hero.jpg?project=store&w=1200&fmt=auto' ``` Check `Content-Type`, `Vary`, `Cache-Control`, `Age`, the CDN's cache-status header, and Keenpix's `X-Keenpix-Cache`. `CF-Cache-Status: HIT` means the request did not reach Keenpix; `X-Keenpix-Cache: HIT` describes the internal layer only when the request did reach the origin. They are different observations. ## Failure signals and likely causes [#failure-signals-and-likely-causes] | Signal | Likely cache-key failure | First check | | ----------------------------------------------- | -------------------------------------------------- | ---------------------------------------------------- | | WebP browser receives AVIF bytes | `Accept` omitted or normalized incorrectly | CDN key and Vary/variant feature | | Correct MIME type but repeated outer `MISS` | URL/query fragmentation or object not eligible | Exact URL, query order, cache rule, response cookies | | Many objects with the same byte hash | Raw `Accept` or unnecessary headers in key | Variant inventory by normalized format | | Origin `MISS` rises after adding Client Hints | Unbounded hint values or changed ladder | Effective width/DPR buckets | | Signed URLs return `403` after canonicalization | Query changed after signing | URL builder and edge rewrite order | | Cloudflare returns `BYPASS` for auto format | Vary for Images rule or extension contract not met | Cloudflare variant configuration | Do not diagnose a low hit ratio from one layer's metric. Edge hits never contact the origin, so an origin-only dashboard cannot count them. The [self-hosting CDN guide](/docs/self-hosting/cdn) describes that measurement boundary. ## Production checklist [#production-checklist] * [ ] Cache only `/img/*`; exclude dashboard, auth, API, and health routes. * [ ] Include scheme, host, complete path, and the full normalized transform query. * [ ] Use explicit `fmt` or prove a bounded `Accept` variant mechanism with three cold/warm tests. * [ ] Keep automatic widths and DPRs on a finite ladder; do not key on arbitrary raw hint strings. * [ ] Ensure watermark and other project-level pixel changes reach the internal key. * [ ] Preserve `sig` in the outer key unless the edge verifies it before lookup. * [ ] Canonicalize query order before signing and cache lookup. * [ ] Record edge and Keenpix-origin cache statuses separately. * [ ] Alert on wrong `Content-Type`, repeated misses, variant-count growth, and origin-transform growth. * [ ] Test purge behavior for every format variant before relying on emergency invalidation. ## Explicit limits [#explicit-limits] This guide does not claim that three formats mean only three total cached images. Width, DPR, quality, crop, source version, watermark configuration, and other transforms remain legitimate dimensions. It also does not claim a universal CDN implementation for `Vary`; verify the provider and plan you operate. The current Keenpix internal format set for automatic negotiation is exactly AVIF, WebP, or JPEG. Explicit format parameters add PNG, GIF, HEIF, TIFF, and SVG paths. Automatic Client Hint bucketing is bounded in repository code, but explicit numeric parameters can still create many valid variants. Use [signed URLs](/blog/signed-image-urls-hmac) when callers should not be able to manufacture new parameter combinations. ## Continue the operations cluster [#continue-the-operations-cluster] Next, read [how to defend image pipelines against SSRF and image bombs](/blog/secure-image-pipelines-ssrf-image-bombs) and [how to prevent cache stampedes and size transform capacity](/blog/image-transform-cache-stampedes-capacity). For codec-specific testing, use [AVIF vs WebP in production](/blog/avif-vs-webp-production-caching). To activate Keenpix, follow the [managed cloud quickstart](/docs/getting-started/cloud-quickstart) or the [self-hosting guide](/docs/self-hosting). ## Sources and verification [#sources-and-verification] Verified August 23, 2026 against [RFC 9111 HTTP Caching](https://www.rfc-editor.org/rfc/rfc9111.html), [RFC 9110 Vary semantics](https://www.rfc-editor.org/rfc/rfc9110.html#name-vary), [Cloudflare Vary for Images](https://developers.cloudflare.com/cache/advanced-configuration/vary-for-images/), [Cloudflare cache keys](https://developers.cloudflare.com/cache/how-to/cache-keys/), [Cloudflare Query String Sort](https://developers.cloudflare.com/cache/advanced-configuration/query-string-sort/), and the official [Nginx proxy cache directives](https://nginx.org/en/docs/http/ngx_http_proxy_module.html). Keenpix observations were checked against the current public [parameter parser](https://github.com/lord007tn/keenpix/blob/master/packages/transform/src/params.ts), [cache coordinator](https://github.com/lord007tn/keenpix/blob/master/packages/cache/src/index.ts), [transform runtime](https://github.com/lord007tn/keenpix/blob/master/apps/transform/src/transform.ts), and their tests. Recommendations are operating patterns, not measured Keenpix performance claims. --- Markdown path: https://keenpix.com/blog/image-cdn-vs-traditional-cdn.md # Image CDN vs traditional CDN: caching and transforms > A traditional CDN caches existing files. An image CDN creates optimized variants and then caches them. Compare responsibilities, costs, and use cases. Canonical HTML: [https://keenpix.com/blog/image-cdn-vs-traditional-cdn](https://keenpix.com/blog/image-cdn-vs-traditional-cdn) Author: Raed Bahri Published: 2026-07-11 Last reviewed: 2026-08-31 The shortest accurate distinction is this: **a traditional CDN caches and delivers an existing file, while an image CDN can transform a source image into a requested variant before caching and delivering it**. A traditional CDN does not become an image optimizer merely because it serves images. An image CDN still relies on CDN principles for distribution and caching. This is not an either-or purchase in every architecture. A transformation service can sit behind a general-purpose CDN, so the edge handles repeat delivery while the image layer handles a cache miss and creates the variant. ## Responsibility matrix [#responsibility-matrix] | Responsibility | Traditional CDN | Image CDN | | ------------------------------------------ | ------------------------- | ----------------------------------------------------- | | Cache an existing URL response | Core responsibility | Usually included or paired with an edge CDN | | Resize to a requested width | Not inherent | Core image operation | | Convert AVIF/WebP based on browser support | Not inherent | Common capability | | Crop, fit, rotate, or adjust quality | Not inherent | Common capability | | Protect and fetch a remote image origin | General origin controls | Image-aware origin and SSRF controls required | | Generate responsive variants | Must be created elsewhere | Generated on demand or ahead of time | | Global edge delivery | Core responsibility | Provider-dependent; may use a bundled or external CDN | | Cache invalidation | URL/header/purge rules | Same concerns plus transformation-key normalization | “Not inherent” is deliberate wording. Some CDN vendors sell image transformation as an additional product. In that case, evaluate the CDN service and image service as separate responsibilities even if they share a dashboard. ## A traditional CDN request [#a-traditional-cdn-request] Suppose a page requests `/images/hero.jpg`. On a cache miss, the CDN retrieves that URL from the configured origin, stores the response according to caching rules, and sends it to the user. A later request for the same cache key can reuse the cached object. If `hero.jpg` is 2400 pixels wide, every user receives that file unless the application or origin provides another URL. The CDN can compress HTTP responses where appropriate, but it does not normally decode the JPEG, resize its pixels, and encode an AVIF simply because the device is smaller. ## An image CDN request [#an-image-cdn-request] An image URL adds transformation intent, for example: ```text /img/https://origin.example.com/hero.jpg?w=800&fmt=auto&q=75 ``` On a transformation cache miss, the image layer validates the origin, fetches the source, resizes it to 800 pixels, chooses or applies an output format, encodes it, and stores that result under a normalized cache key. A CDN can then cache the generated response at the edge. The transformation creates more possible cache objects. Width, height, crop, quality, DPR, format, and other parameters can multiply variants, so a production design needs limits and URL signing. “Unlimited transformations” in a price list does not remove the operational need to control arbitrary combinations. ## Where should each cache live? [#where-should-each-cache-live] There may be several caches: 1. browser cache on the visitor's device; 2. edge cache in a traditional CDN; 3. generated-variant cache near the transformation service; 4. original image storage or an origin cache. The best result is not necessarily the most caches. Each layer needs coherent `Cache-Control`, stable URLs, and an invalidation strategy. If an image changes without its URL changing, a year-long immutable response can keep stale content alive. Versioned asset URLs are simpler than purging every layer reliably. The [MDN Cache-Control reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control) documents the response directives shared by both architectures. Provider-specific cache keys and purge semantics still require provider documentation and testing. ## How billing models differ [#how-billing-models-differ] A traditional CDN commonly meters delivered data, requests, or both. An image service may meter any combination of transformations, unique source images, generated variants, storage, cache operations, or delivered bandwidth. Bundling the products does not make those meters equivalent. Compare a real workload rather than an abstract unit price: * number and average size of source images; * responsive widths and device-pixel-ratio variants; * format variants; * monthly requests and delivered bytes after edge-cache hits; * source-change and invalidation frequency; * cold-transform traffic; * limits, overage rules, and failure behavior. Do not label one provider “cheapest” without publishing those assumptions. Negotiated contracts, region, cache hit rate, and legacy plans can reverse a simple table. ## When a traditional CDN is enough [#when-a-traditional-cdn-is-enough] A traditional CDN may be enough when a build pipeline already creates every required format and width, image URLs are content-hashed, assets change only on deployment, and the application emits correct responsive markup. This approach is particularly attractive for a small static library because it removes runtime image processing from the delivery path. It may also be enough when images are already tiny and correctly sized. Adding a transformation dependency to save a negligible number of bytes creates complexity without a meaningful user benefit. ## When an image CDN adds value [#when-an-image-cdn-adds-value] An image CDN is more useful when sources arrive after build time, a catalog is large, many frameworks need one URL grammar, or a team cannot predict every required dimension. It can also centralize format negotiation, quality policy, origin controls, and delivery analytics. The tradeoff is a new production dependency. You need a behavior for cache misses, upstream failures, abuse, spending limits, migrations, and provider outages. A self-hosted transform service changes who owns those problems; it does not make them disappear. ## Can you use both? [#can-you-use-both] Yes. A common architecture is: ```text browser → edge CDN → image transformation service → protected image origin ``` The edge answers repeat requests. The image service creates a variant only when needed. The protected origin stores the master. This separation lets a team keep an existing CDN while choosing a different transform layer, but only if cache headers and host routing are configured correctly. Keenpix is designed around that separation: it transforms and caches images from allowlisted origins and can sit behind a CDN you already operate. That is a product design statement, not evidence that it is the right architecture for every site. ## Decision checklist [#decision-checklist] Choose the smallest system that meets the requirement: * If variants are known at build time, test a build pipeline plus traditional CDN first. * If images or requested sizes are dynamic, test an image CDN with representative traffic. * If you already use a CDN's image add-on, model that add-on separately from ordinary cache delivery. * If exit control matters, document how URLs and source assets move before adoption. * In every case, measure rendered dimensions, transferred bytes, cache hit rate, failures, and user-visible loading. For a deeper lifecycle explanation, read [what is an image CDN?](/blog/what-is-an-image-cdn). For browser markup and sizing, use the [responsive image CDN guide](/blog/responsive-image-cdn-guide). If the decision is now vendor-specific, use the source-dated [Cloudinary](/compare/cloudinary-alternative) or [imgix](/compare/imgix-alternative) worksheet and canary method instead of comparing headline plan prices alone. ## Sources and verification [#sources-and-verification] This article was checked on July 11, 2026 against [web.dev's image CDN overview](https://web.dev/articles/image-cdns), the [MDN HTTP caching guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching), and the [MDN Cache-Control reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control). The architecture and cost examples are qualitative and do not claim measured Keenpix or competitor performance. --- Markdown path: https://keenpix.com/blog/image-delivery-troubleshooting-by-symptom.md # Image delivery troubleshooting: diagnose by symptom > Trace broken, slow, stale, oversized, or incorrectly cropped images from browser selection through caches, transforms, and origin access. Canonical HTML: [https://keenpix.com/blog/image-delivery-troubleshooting-by-symptom](https://keenpix.com/blog/image-delivery-troubleshooting-by-symptom) Author: Raed Bahri Published: 2026-09-02 Last reviewed: 2026-09-02 Troubleshoot image delivery from the visible symptom backward: browser selection, response headers, delivery cache, transform service, then origin. Preserve one failing URL and one known-good URL before changing anything. Random purges and quality changes often erase the evidence without fixing the cause. ## Capture a minimal evidence bundle [#capture-a-minimal-evidence-bundle] For one failing request, record: ```text Page URL: Image request URL with secrets removed: UTC timestamp: Browser and viewport: currentSrc: HTTP status: Content-Type: Cache-Control / Age / ETag / Vary: Decoded and natural dimensions: Cold and immediate-repeat timings: Origin object status and MIME type: Recent configuration or release change: ``` Do not paste API keys, signing secrets, cookies, private origin URLs, or customer data into tickets. A redacted URL must retain the hostname, path shape, parameter names, and status needed to reproduce the class of failure. ## Symptom: the image is broken or returns 4xx [#symptom-the-image-is-broken-or-returns-4xx] ### 400 or 422 [#400-or-422] Suspect an invalid parameter, unsupported format, dimension limit, malformed source encoding, or signature input mismatch. Compare the URL against the documented parameter names and canonical order. If a signature covers the path or query, changing even an encoded slash can invalidate it. ### 401 or 403 [#401-or-403] Identify which layer rejected the request. A delivery signature failure, CDN firewall rule, transformer allowlist, origin authorization policy, and expired presigned URL can all return 403. Response headers and logs should name the layer without exposing credentials. ### 404 [#404] Check whether the delivery route exists before assuming the source is absent. Then decode the embedded source URL and request it from the transformer's network context. Object-store keys are often case-sensitive. Redirecting a missing path to an HTML error page can turn a clear 404 into a later decoder failure. ### 429 [#429] Determine whether the limit belongs to the edge, transform service, origin, or account. Preserve `Retry-After` if provided. Retrying every image immediately can amplify a small capacity problem into a stampede. ## Symptom: 5xx, timeouts, or intermittent failures [#symptom-5xx-timeouts-or-intermittent-failures] | Pattern | Likely boundary | Next check | | ------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------- | | Cached assets work; new variants fail | origin fetch or transformer | request an uncached width and inspect origin reachability | | One region fails | regional edge, DNS, or upstream path | compare resolver, route, and response headers by region | | Large images fail | byte, pixel, memory, or timeout limit | test around documented input limits with safe fixtures | | Failure follows a release | application or configuration change | compare exact revision and configuration, then exercise rollback | | Immediate retry succeeds | cold capacity, race, or transient upstream | inspect queueing, connect time, and cache-fill coordination | Use bounded retries only for idempotent reads and add jitter. A transform request may be safe to retry, but a purge, key rotation, or project mutation needs its own idempotency design. ## Symptom: the image is slow [#symptom-the-image-is-slow] Split the timing into DNS, connection, TLS, time to first byte, transfer, decode, and render. A large duration alone does not tell you whether the source, transform, cache, network, or browser is responsible. 1. Repeat the exact URL. If the second response is much faster, investigate cold transform and cache-fill behavior. 2. Inspect `Age`, provider cache headers, and `Server-Timing` if present. A faster repeat without a cache signal is not proof of a hit. 3. Request the source from the transformer's region. A fast origin from your laptop can still be slow from the service. 4. Compare transferred bytes and decoded dimensions with the rendered slot. 5. Check whether the image was discovered late because of CSS, client rendering, lazy loading, or an incorrect priority hint. 6. Use the [reproducible measurement protocol](/blog/reproducible-image-performance-measurement) before comparing providers. ## Symptom: the image is too large [#symptom-the-image-is-too-large] Inspect `Content-Type`, `Content-Length`, dimensions, quality, metadata, and animation. Common causes include an explicit PNG output for a photograph, a browser selecting the largest `srcset` candidate because `sizes` is wrong, format negotiation cached without `Vary`, an animated source that bypasses the intended path, or a transformer preserving dimensions the layout never uses. Do not reduce quality globally as the first fix. That can damage text and product-detail images while leaving the selection error intact. ## Symptom: the crop is wrong or blurry [#symptom-the-crop-is-wrong-or-blurry] Compare four sizes: source pixels, requested output, decoded output, and CSS-rendered box. Then check `object-fit`, aspect ratio, crop mode, focal position, device pixel ratio, and upscaling policy. * Blurry only on dense screens usually means the candidate ladder stops too early. * A correct desktop crop and wrong mobile crop usually means one focal rule is being reused across different aspect ratios. * A sharp image that shifts the layout needs explicit intrinsic dimensions or `aspect-ratio`; more pixels will not fix the shift. * Text or line art can look worse at a lower lossy quality even when photographs look acceptable. ## Symptom: stale or inconsistent images [#symptom-stale-or-inconsistent-images] First ask whether the URL is immutable. If the source bytes can change at the same URL, every cache between the origin and browser needs a refresh policy. Compare `ETag`, `Last-Modified`, `Age`, cache key, purge scope, and any stale-serving directive. Versioned asset URLs are usually safer than global purges: ```text /product/hero.jpg?v=2026-09-02 ``` If format negotiation is automatic, request the same URL with explicit `Accept: image/avif`, `Accept: image/webp`, and a legacy value. An AVIF response reused for a client that did not request it points to incomplete cache variation. See [cache keys and Vary](/blog/image-cdn-cache-keys-vary-accept). ## Verify the fix [#verify-the-fix] * Reproduce the original failure before applying the change. * Change one boundary at a time. * Repeat the original URL and a new uncached variant. * Test a supported client, an unsupported format, an allowed source, a blocked source, a large safe fixture, and a normal fixture. * Check desktop and mobile `currentSrc`, output dimensions, visual crop, and layout shift. * Confirm logs identify the rejection layer without recording secrets. * Leave a rollback trigger and observation window proportional to cache lifetime. ## Using Keenpix during diagnosis [#using-keenpix-during-diagnosis] For Keenpix, start with the response status and documented [transform endpoint](/docs/reference/endpoint), then check project origin allowlists, signed URL configuration, and delivery analytics. Self-hosted operators should also check the [health and operations guide](/docs/self-hosting/health-and-operations). Managed users can provide a redacted failing URL and timestamp through [support](/support). Keenpix is not the right diagnostic target when the browser never requests the Keenpix URL, the origin object itself is broken, or another CDN rewrites or blocks the response first. Fix the owning layer or remove the intermediary before attributing the symptom. ## Sources and limitations [#sources-and-limitations] * [Chrome DevTools Network reference](https://developer.chrome.com/docs/devtools/network/reference/), reviewed September 2, 2026. * [HTTP response status codes, MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status), reviewed September 2, 2026. * [HTTP caching, MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching), reviewed September 2, 2026. * [Resource Timing Level 2, W3C](https://www.w3.org/TR/resource-timing-2/), reviewed September 2, 2026. Header names differ by CDN, and a cache hit does not prove a globally healthy service. This procedure produces a reproducible boundary diagnosis, not an uptime or performance guarantee. ## Related guides [#related-guides] * [Transform cache stampedes and capacity](/blog/image-transform-cache-stampedes-capacity) * [Secure image pipelines](/blog/secure-image-pipelines-ssrf-image-bombs) * [Safe image CDN rollouts and rollbacks](/blog/safe-image-cdn-rollouts-and-rollbacks) --- Markdown path: https://keenpix.com/blog/image-transform-cache-stampedes-capacity.md # Stop cache stampedes in image transformation > Coalesce cold image transforms, serve stale safely, shed overload, and size CPU and memory capacity from measured service time and working sets. Canonical HTML: [https://keenpix.com/blog/image-transform-cache-stampedes-capacity](https://keenpix.com/blog/image-transform-cache-stampedes-capacity) Author: Raed Bahri Published: 2026-08-23 Last reviewed: 2026-09-03 Prevent an image cache stampede at three boundaries: let the outer cache collapse a cold fill, coalesce identical work inside the transform service, and admit only as many distinct cold transforms as the CPU and memory budget can finish safely. Serve stale bytes during refresh when the image contract permits it. Reject excess cold work quickly instead of building an unbounded queue. A single-flight map solves “100 requests for the same key.” It does not solve “100 requests for 100 different widths,” a multi-replica cold start, or a purge that invalidates a large catalog. Capacity planning starts where key-level coalescing ends. ## The desired cold-request path [#the-desired-cold-request-path] ```text clients │ ▼ edge cache ── warm/stale ───────────────────────────────> bytes │ cold key ▼ shared cache-fill lock ── followers wait with a deadline │ leader ▼ origin shield / Keenpix cache ── hit ───────────────────> bytes │ distinct cold transform ▼ bounded admission ── full ──> 503 + short Retry-After │ admitted ▼ fetch within byte/time limits → decode/transform → durable cache write → bytes ``` The lock is per effective representation, while admission is global or tenant-partitioned. Mixing them is a common mistake. A thousand unique cold keys pass through a thousand independent locks and can still overload the host. ## Observation: what Keenpix does today [#observation-what-keenpix-does-today] Verified from the public repository on August 23, 2026: * Keenpix checks memory, then Dragonfly when configured, then S3-compatible object storage; local development can use disk instead. Lower-tier hits are promoted upward, and writes go to the durable tier before hot tiers. * A stale internal entry is returned immediately. One background refresh starts when the current process has no refresh for that key. * A missing key is stored in an in-process `Map` while fetch, transform, and cache write run. Other requests for the same hash await that promise instead of repeating the transform. * The key contains project id, source URL, and full effective transform options. Different widths, formats, qualities, crops, or watermark configuration are deliberately different work. * The Sharp wrapper sets `sharp.concurrency(1)`, limiting libvips to one worker thread per image. Sharp documents that some codec libraries, including AVIF encoders, can create additional threads independently. * `KEENPIX_WORKER_CONCURRENCY` controls durable prewarm jobs. It does not bound normal synchronous HTTP image transforms. The current single-flight map is process-local. Two transform replicas can both miss shared storage and perform the same transform. The current public HTTP transform path also has no application-level global semaphore or bounded wait queue. Health readiness can return `503` for failed dependencies, but that is not general overload shedding. Those are explicit product limits, not measured incidents. The recommendations below describe how an operator can add the missing outer/shared and admission boundaries without claiming that Keenpix already supplies them. ## Four different stampedes [#four-different-stampedes] | Event | Key pattern | Why one in-process lock is insufficient | Primary control | | --------------------------------------- | ---------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------- | | Viral cold image | Many requests, one key, one process | Usually sufficient locally; replicas may duplicate | Edge fill lock plus local single-flight | | Deploy or restart | Many hot keys absent from memory | Distinct keys bypass one another | Durable shared cache, prewarm budget, admission limit | | Purge or terminal expiry | Many previously warm keys become cold together | Invalidation synchronizes demand | Versioned URLs, staged purge, stale serving | | Variant attack or bad responsive config | Many unique widths/qualities/queries | Every request owns a different key | Bounded ladders, signing, rate limits, cardinality alerts | The first response is not “add more retries.” Retries multiply load unless they are limited, delayed, and jittered. If a transform returns `503`, the caller should retry only when the request still matters, honor `Retry-After`, use exponential backoff with jitter, and cap attempts. ## Add a cache-fill lock at the proxy [#add-a-cache-fill-lock-at-the-proxy] Nginx documents `proxy_cache_lock` specifically to allow one request at a time to populate a new cache element while followers wait for the cached response or a lock timeout. ```nginx proxy_cache_path /var/cache/nginx/keenpix levels=1:2 keys_zone=keenpix:20m max_size=20g inactive=30d use_temp_path=off; location /img/ { proxy_pass http://keenpix_transform; proxy_cache keenpix; proxy_cache_key "$scheme$host$request_uri"; proxy_cache_lock on; # Illustrative only: replace these from measured cold-fill latency and the # end-to-end client deadline. Do not copy them as universal safe values. proxy_cache_lock_timeout 20s; proxy_cache_lock_age 30s; proxy_cache_background_update on; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; add_header X-Cache-Status $upstream_cache_status always; } ``` This example assumes explicit output formats or separately proven `Vary: Accept` behavior. Read the [cache-key and Vary guide](/blog/image-cdn-cache-keys-vary-accept) before copying it. The `20s`/`30s` values are placeholders, not Keenpix defaults. Set `proxy_cache_lock_timeout` above the measured high-percentile cold-fill duration plus margin and below the end-to-end client deadline. Nginx documents that a waiter whose lock timeout expires is passed to the upstream and its response is not cached. If many waiters expire together, they can fan out into duplicate transforms. `proxy_cache_lock_age` is a separate escape hatch: when the active fill exceeds that age, Nginx may pass one more request upstream. Therefore, a cache lock is not an admission controller. Pair it with the bounded cold-work gate in the next section before production, and alert on lock timeouts and age releases. If no timeout fits between measured cold latency and the client deadline, fail or serve stale at a real admission boundary instead of relying on the lock to protect capacity. An unlimited wait merely moves an unbounded queue into the proxy. `proxy_cache_use_stale updating` and background update can keep serving a previous image while one refresh runs. RFC 5861 defines the same availability pattern through `stale-while-revalidate` and `stale-if-error`. Use stale delivery only when the URL is versioned or the business accepts temporary old bytes. Do not serve a stale private or revoked image merely to improve availability. ## Bound distinct cold work [#bound-distinct-cold-work] Use a semaphore or proxy/orchestrator admission limit around cold transforms, not around cache hits. A practical policy has three outcomes: ```text warm hit -> serve immediately cold + slot -> transform cold + no slot -> wait for a very short bounded interval, then 503 ``` At Nginx, a simple connection boundary can shed excess requests while you implement a cold-aware application gate: ```nginx limit_conn_zone $server_name zone=keenpix_transform_conn:10m; location /img/ { limit_conn keenpix_transform_conn 16; limit_conn_status 503; proxy_connect_timeout 2s; proxy_read_timeout 20s; proxy_send_timeout 20s; proxy_pass http://keenpix_transform; } ``` This counts proxied connections, so it is less efficient than a gate that distinguishes an internal cache hit from a cold transform. It is still a safer emergency ceiling than allowing memory to determine the limit through an OOM kill. Preserve a separate lightweight health route outside the limit. Do not reuse `KEENPIX_WORKER_CONCURRENCY` as if it controlled HTTP traffic. Reduce or pause prewarm concurrency during a live cold-miss surge; background warming should yield to user requests. ## Request-rate caps and cold-work admission are different [#request-rate-caps-and-cold-work-admission-are-different] A public request-rate limit answers “how many requests may this caller, tenant, or route send in a window?” A cold-work admission limit answers “how many distinct transforms may execute concurrently without exhausting CPU, memory, storage, or origin capacity?” You usually need both. A hot cached image can tolerate a much higher request rate than an uncached stream of unique widths. Conversely, a low-rate request for one huge animated source can consume more work than hundreds of small hits. Apply an edge rate cap before the transform service to contain floods and abusive clients, but keep a separate semaphore or queue budget around distinct cold transforms. Keenpix's current public repository does not declare one universal application-level requests-per-second limit for image delivery. Project transformation constraints, signed URLs, origin allowlists, proxy limits, CDN rules, container capacity, and provider plan policy are separate boundaries. Do not invent an RPS number from the prewarm worker concurrency or from a load test on different hardware. When a request cap is exceeded, prefer `429 Too Many Requests` with a bounded `Retry-After` when the rejection is specifically caller quota. Use `503 Service Unavailable` when shared transform capacity is temporarily full. Keep either response out of the image's immutable success cache. Track hot hits, unique cold keys, rejected quota requests, admission rejects, queue age, and retry volume separately; one aggregate request graph hides the failure mode. ## Capacity is constrained by CPU, memory, and origins [#capacity-is-constrained-by-cpu-memory-and-origins] Benchmark on the same CPU architecture, container limits, Sharp/libvips build, and formats used in production. Build a representative fixture set: * small JPEG thumbnails; * large camera JPEGs near the pixel ceiling; * transparent PNG product images; * WebP and AVIF inputs; * animated inputs only if enabled; * watermark and expensive crop/effect combinations; * slow and fast origin responses. For every fixture, record cold service time, CPU time, peak RSS delta, origin bytes, output bytes, format, dimensions, and whether the durable cache write completed. Do not publish a universal “transforms per second” number from one synthetic image. Use two starting calculations: ```text memory-bound slots = floor((container memory limit - baseline RSS - safety headroom) / measured p95 peak RSS delta per transform) CPU-bound slots ≈ floor(usable CPU cores / measured p95 core demand per transform) ``` Choose the lower result, then lower it again if origin sockets, object-storage writes, or tail latency fail first. These are planning formulas, not queueing guarantees. Workloads overlap imperfectly, native memory is not always returned immediately, and format libraries can use threads outside Sharp's configured libvips concurrency. Keep explicit headroom for the runtime, memory cache, network buffers, native allocator fragmentation, logging, and health checks. Kubernetes documents that CPU limits throttle CPU time while exceeding a memory limit can invoke the OOM subsystem and terminate the process. A memory limit is a final containment boundary, not an admission algorithm. ## Run a stepped load test, not one maximum blast [#run-a-stepped-load-test-not-one-maximum-blast] 1. Warm the durable cache and measure the hit path separately. 2. Purge only the controlled fixture keys. 3. Send one cold request at a time to establish service-time and memory baselines. 4. Increase distinct-key concurrency in small steps: `1, 2, 4, 6, 8...`. 5. Hold each step long enough to observe steady CPU, RSS, queue depth, and p95/p99 latency. 6. Stop before OOM, sustained CPU throttling, growing queues, origin saturation, or readiness failure. 7. Repeat with identical-key bursts to verify coalescing independently. 8. Repeat across two replicas to expose duplicate work that a process-local map cannot prevent. Capacity is the highest step that meets the error and latency objective with headroom, not the last step before the container dies. Document the fixture mix and build identifiers alongside the result. ## Metrics that reveal overload early [#metrics-that-reveal-overload-early] | Signal | Healthy interpretation | Overload/stampede signal | | ---------------------------- | ------------------------------------- | -------------------------------------------------- | | Edge cache status | Repeat traffic becomes `HIT` | Burst of `MISS`/`EXPIRED` across many keys | | Keenpix cache status | Outer misses often hit the shield | Internal `MISS` rises with edge misses | | In-flight unique keys | Bounded and returns to baseline | Grows continuously or tracks request concurrency | | Coalesced followers | Brief spikes on popular keys | High waits plus lock timeouts | | Transform duration | Stable by format/dimension class | p95/p99 rises before throughput does | | Sharp queue/process counters | Queue drains after bursts | Queue grows while process count stays saturated | | CPU throttling | Low under target load | Sustained throttling and tail-latency growth | | RSS / OOM / restarts | RSS returns near baseline | High-water growth, OOM kill, restart loop | | Origin fetch time/errors | Within source-specific objective | Socket growth, `504`, or one host dominates slots | | Durable cache write failures | Rare and recoverable | Repeated recomputation because fills never persist | | Prewarm queue age | Drains without affecting live traffic | Oldest job grows while live cold latency degrades | Keenpix exposes transform liveness/readiness and a separate worker Workbench for prewarm jobs. Keep those service-local operator surfaces private. See [health and operations](/docs/self-hosting/health-and-operations) for the probe boundary. ## Failure patterns and first actions [#failure-patterns-and-first-actions] ### One hot key, many origin fetches [#one-hot-key-many-origin-fetches] Confirm that the public cache key is identical, query order is canonical, and format variation is bounded. Then test the same burst against one process and multiple replicas. If one process coalesces but two do not, add an outer cache-fill lock or a distributed lease with a short expiry and owner token. ### Many unique misses after a frontend release [#many-unique-misses-after-a-frontend-release] Inspect emitted widths, DPRs, qualities, and query parameters. Roll back an unbounded responsive ladder, not the cache. Enable [signed URLs](/docs/concepts/signed-urls) if untrusted callers can manufacture variants. ### RSS climbs while CPU is not saturated [#rss-climbs-while-cpu-is-not-saturated] Reduce admission concurrency, inspect input pixels/animation and Sharp cache statistics, and verify container memory limits. Do not raise the memory limit until the fixture identifies whether the increase is expected working set, native fragmentation, or retained data. ### CPU is saturated and latency grows [#cpu-is-saturated-and-latency-grows] Reduce cold slots or scale replicas only if the shared cache and origin can support them. More replicas without an outer fill lock may multiply identical cold work. Prefer explicit WebP/JPEG during an incident if AVIF encoding is the dominant cost and that format change is compatible with the URL contract. ### Cache writes fail [#cache-writes-fail] Serve the completed response if policy allows, but alert immediately: every subsequent request may recompute the same key. Check Dragonfly/object-storage readiness, credentials, capacity, latency, and whether the durable tier is written before hot-tier promotion. ## Safe retry contract [#safe-retry-contract] When admission is full, return a short response: ```http HTTP/1.1 503 Service Unavailable Cache-Control: no-store Retry-After: 2 Content-Type: text/plain; charset=utf-8 Image transform capacity is temporarily full. ``` Do not cache the overload response under the image's long immutable policy. At clients or prewarm workers, cap retries and add jitter: ```ts const delayMs = Math.min(8_000, 250 * 2 ** attempt) const jitteredMs = Math.round(delayMs * (0.5 + Math.random())) ``` Interactive browser image loads may not implement application retries at all; the CDN's stale/error policy is usually the better availability mechanism. Durable prewarm work can retry later because it is not on the user path. ## Production checklist [#production-checklist] * [ ] Use a correct, bounded cache key before optimizing hit rate. * [ ] Enable one cache-fill leader per outer-cache key with a finite wait and lock age. * [ ] Serve stale during refresh only for content whose freshness policy allows it. * [ ] Coalesce identical work inside each process and test cleanup after success and failure. * [ ] Add a global or tenant-aware limit for distinct cold transforms. * [ ] Keep the waiting queue short and return `503`/`Retry-After` before memory exhaustion. * [ ] Measure representative cold transforms on the production CPU and memory boundary. * [ ] Set concurrency from the lower CPU/memory/origin constraint with explicit headroom. * [ ] Reserve live capacity; pause or reduce prewarm work during demand spikes. * [ ] Version URLs and stage purges instead of invalidating an entire hot catalog at once. * [ ] Monitor edge and internal cache status, unique in-flight keys, Sharp counters, CPU throttling, RSS, OOMs, origin failures, and cache-write failures. * [ ] Test one process and multiple replicas; document where duplicate work can occur. ## Explicit limits and non-guarantees [#explicit-limits-and-non-guarantees] Keenpix currently coalesces identical requests and refreshes stale internal entries within a process. It does not currently promise a distributed single-flight lock, a configurable HTTP transform semaphore, or a universal `503` overload response. `KEENPIX_WORKER_CONCURRENCY` is limited to prewarm workers. This guide provides no Keenpix throughput, latency, cache-hit, or memory guarantee. The safe slot count depends on sources, pixels, formats, effects, native libraries, hardware, container limits, and traffic mix. Use the [parameter reference](/docs/reference/parameters) and [input-security guide](/blog/secure-image-pipelines-ssrf-image-bombs) to bound the worst cases before load testing. ## Continue the operations cluster [#continue-the-operations-cluster] Start with [image CDN cache keys and Vary: Accept](/blog/image-cdn-cache-keys-vary-accept), then apply [SSRF, redirect, byte, and image-bomb defenses](/blog/secure-image-pipelines-ssrf-image-bombs). The [AVIF vs WebP production guide](/blog/avif-vs-webp-production-caching) covers format-specific cache testing. For activation, follow the [managed cloud quickstart](/docs/getting-started/cloud-quickstart), [start a trial](/signup), or use the [self-host deployment path](/docs/self-hosting). ## Sources and verification [#sources-and-verification] Verified August 23, 2026 against the official [Nginx proxy cache lock and stale directives](https://nginx.org/en/docs/http/ngx_http_proxy_module.html), [RFC 5861 stale-while-revalidate and stale-if-error](https://www.rfc-editor.org/rfc/rfc5861.html), [Sharp queue, cache, counters, and concurrency documentation](https://sharp.pixelplumbing.com/api-utility/), [AWS overload mitigation guidance](https://docs.aws.amazon.com/prescriptive-guidance/latest/resilience-analysis-framework/mitigation-strategies.html), and [Kubernetes container resource behavior](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). Keenpix observations were checked against the current public [transform runtime](https://github.com/lord007tn/keenpix/blob/master/apps/transform/src/transform.ts), [cache coordinator](https://github.com/lord007tn/keenpix/blob/master/packages/cache/src/index.ts), [Sharp wrapper](https://github.com/lord007tn/keenpix/blob/master/packages/transform/src/sharp/index.ts), [prewarm worker](https://github.com/lord007tn/keenpix/blob/master/packages/bullmq/src/workers/prewarm.ts), and tests. Capacity formulas and limits are recommendations to validate, not product benchmarks. --- Markdown path: https://keenpix.com/blog/joodcms-keenpix-integration.md # How JoodCMS integrates Keenpix image delivery > A code-backed case study of JoodCMS using Keenpix for responsive variants, source allowlists, domain sync, prewarming, and safe fallbacks. Canonical HTML: [https://keenpix.com/blog/joodcms-keenpix-integration](https://keenpix.com/blog/joodcms-keenpix-integration) Author: Raed Bahri Published: 2026-07-13 Last reviewed: 2026-07-13 [JoodLab](https://www.joodlab.com/) maintains JoodCMS, a multi-tenant TypeScript CMS with several Nuxt storefront themes. JoodCMS now contains a first-party Keenpix integration for turning CMS media URLs into responsive image variants while keeping project access, source hosts, and failures explicit. This is a **code-backed implementation case study**, not a performance testimonial. We reviewed the JoodCMS integration source on July 13, 2026. No traffic volume, byte savings, cache-hit rate, Core Web Vitals improvement, or cost reduction is claimed here because those outcomes have not yet been published from a reproducible production measurement. **Relationship disclosure:** JoodCMS and Keenpix are projects in the same JoodLab-led product ecosystem. This is first-party implementation evidence, not an independent customer endorsement. ## The integration boundary [#the-integration-boundary] JoodCMS separates two kinds of access: * A trusted backend stores a Keenpix API key and a project ID for SDK management calls. * Public storefront image URLs carry the project ID but no secret. Keenpix checks the requested source host against that project's allowlist. That follows Keenpix's documented [project and access model](/docs/getting-started/projects): the transform URL is safe to put in HTML, while the management key stays server-side. The JoodCMS integration directs its configuration, domain, and prewarm requests to one named project instead of using a global image setting shared across unrelated sites. The matching Keenpix key is created inside that organization and project, so it cannot administer sibling projects. ## How a storefront image is built [#how-a-storefront-image-is-built] Each supported JoodCMS Nuxt theme registers a custom Nuxt Image provider. For an eligible raster source, the provider: 1. keeps the original absolute source URL; 2. encodes it into Keenpix's `/img/` path; 3. adds the configured project ID; 4. forwards the width, quality, and requested output format; and 5. leaves visual cropping to the storefront's CSS instead of forcing an unrelated source image into one server-side aspect ratio. The provider also refuses to re-wrap a URL that is already a Keenpix transform and leaves non-raster assets untouched. That matters in a CMS, where logos, SVGs, animated media, and externally managed files may share the same content model. For the URL grammar and supported parameters, see the [transform endpoint reference](/docs/reference/endpoint) and [Nuxt integration guide](/docs/frameworks/nuxt). ## Domain changes update the source allowlist [#domain-changes-update-the-source-allowlist] CMS sites change domains: a preview hostname becomes a launch hostname, a `www` alias is added, or an old domain is removed. JoodCMS connects those website-domain workflows to Keenpix's project-domain endpoints. The integration normalizes the hostname and calls the authenticated SDK endpoint to add or remove it from the project's allowed sources. A manual sync action can reconcile the current set of website hosts. Failed synchronization is logged and reported instead of silently turning a rejected origin into an unrestricted fetch. This is the important security property: JoodCMS can automate normal domain lifecycle work without weakening Keenpix's [source validation and SSRF boundary](/docs/concepts/security). ## Prewarming after media changes [#prewarming-after-media-changes] JoodCMS also implements a prewarm action for recently uploaded or published images. Its authored defaults request the widths `320`, `640`, `768`, `960`, and `1280`, with AVIF and WebP variants at quality 75. Those are inputs, not a universal recommendation. A storefront should choose widths from its real rendered layouts and verify that its `sizes` attribute lets the browser select the intended candidate. The action accepts up to the limits documented by Keenpix and sends the job to the project-specific [SDK prewarm endpoint](/docs/reference/sdk-api#prewarm). Prewarming reduces the chance that the first visitor waits for a cold transformation, but it does not prove a performance improvement by itself. The production check still needs browser timings, cache headers, response formats, and image dimensions. ## Failure behavior stays visible and reversible [#failure-behavior-stays-visible-and-reversible] The JoodCMS image component marks a Keenpix delivery failure and retries the original source without the custom provider. This protects page rendering when an integration setting, origin rule, or transform request is wrong. Fallback is a safety net, not observability. A team should still monitor how often it occurs: a page that remains visible while repeatedly serving oversized originals is available, but not healthy. ## What we would measure next [#what-we-would-measure-next] The useful production comparison is not “Keenpix on” versus an invented benchmark. It is a controlled sample of the same JoodCMS pages and source images, recording: * selected candidate width and output format; * transformed bytes versus original-source bytes; * cold and warm response timing; * edge and application cache status; * LCP for pages where the image is actually the LCP element; and * fallback and transform error rates. The source list, exact URLs, browser profile, geography, cache state, and test date should ship with any published result. Until then, the defensible result is the architecture itself: JoodCMS has a project-targeted, allowlist-aware, framework-native integration with prewarming and source fallback. ## Implementation references [#implementation-references] The reviewed JoodCMS repository is private, so this article names the source paths rather than linking readers to inaccessible files: * `packages/integrations/keenpix/src/index.ts` — integration registration and stored configuration; * `packages/integrations/keenpix/src/actions/prewarm.ts` — reusable prewarm action; * `apps/server/src/actions/keenpix.ts` — project configuration, domain sync, and backend prewarm client; * `apps/theme-default-*/app/providers/keenpix.ts` — Nuxt Image URL construction; and * `apps/theme-default-*/app/components/CmsImage.vue` — provider selection and source fallback. For a similar implementation, start with the [cloud quickstart](/docs/getting-started/cloud-quickstart), then use the [SDK API](/docs/reference/sdk-api) for project automation. For the responsive HTML side, read the [responsive image CDN guide](/blog/responsive-image-cdn-guide). *Editorial disclosure: the article's text-free hero artwork was generated with OpenAI image generation, cropped into the documented Keenpix cover and social-card formats, and visually reviewed against the Keenpix brand guide. It does not depict measured customer data or a literal JoodCMS screen.* --- Markdown path: https://keenpix.com/blog/keenpix-vs-cloudinary.md # Cloudinary Credits Explained: Pricing vs Keenpix > Learn how Cloudinary credits combine transformations, storage, and bandwidth, then compare that meter with Keenpix managed-delivery pricing. Canonical HTML: [https://keenpix.com/blog/keenpix-vs-cloudinary](https://keenpix.com/blog/keenpix-vs-cloudinary) Author: Raed Bahri Published: 2026-07-06 Last reviewed: 2026-08-31 Cloudinary is an established image and video platform with a broad product surface. This guide focuses narrowly on how its credit meter works and how to model it against Keenpix managed-delivery pricing. If you are choosing a vendor rather than researching the meter, use the sourced [Keenpix vs Cloudinary decision guide](/compare/cloudinary-alternative). ## The core difference: credits vs. managed delivery [#the-core-difference-credits-vs-managed-delivery] Cloudinary meters usage in **credits**. A single credit bundles together a mix of transformations, managed storage, and delivered bandwidth, and each of those consumes credits at a different rate. The result is a bill that is hard to predict and harder to explain: a viral asset, a bot crawl, or a new responsive-image breakpoint can quietly burn through a month's allotment. Keenpix meters **optimized bytes delivered through managed cloud**. Managed Cloudflare edge hits and application responses count once, while transformations and team members are included and Keenpix does not sell asset storage. | | Keenpix | Cloudinary | | --------------------- | ---------------------------------------------- | ------------------------------------------------------------- | | Billing unit | Keenpix managed delivery (GB) | Credits (bundled) | | Transformations | Unlimited, free | Consume credits | | Overage | Founding $0.05–0.08/GB; standard $0.07–0.12/GB | Credit packs / plan upgrades | | Paid overage behavior | Always on, billed at period end | Check account terms | | Self-host option | Yes (open source, AGPL) | No | | Entry price | $9/mo | $99/mo monthly for Plus ($89/mo equivalent on annual billing) | *Re-verified August 31, 2026 against [Cloudinary pricing](https://cloudinary.com/pricing), its [credit definition and plan comparison](https://cloudinary.com/pricing/compare-plans), and the [credit documentation](https://cloudinary.com/documentation/developer_onboarding_faq_credits). Cloudinary describes plan limits as soft limits and contacts customers about upgrading after an overage; exact commercial handling can depend on the account.* If pooled credits are the difficult part of your forecast, enter delivered GB, source storage, and unique transforms in the [image CDN cost calculator](/image-cdn-cost-calculator). Its Cloudinary row shows the estimated credit total and smallest published self-serve package that covers it; negotiated terms and other Cloudinary products remain outside the model. ## Predictable pricing [#predictable-pricing] Keenpix plans start at **$9/month for 100 GB of managed delivery**. Pro is $29 for 400 GB and Business is $69 for 1 TB, with $0.12/$0.09/$0.07 per delivered GB of overage. Paid delivery continues while the dashboard shows usage and projected charges. Because transforms are free, you never have to think about how many derived variants a responsive `srcset` generates, or whether adding an AVIF variant will move your bill. You optimize aggressively without being punished for it. ## You can run it yourself [#you-can-run-it-yourself] This is the difference no amount of pricing tweaking can close: **the Keenpix cloud release publishes its image engine under AGPL-3.0**. You can deploy that version with Docker on your own infrastructure without a Keenpix license fee. A move still requires infrastructure, operations, and routing work; the transform URL grammar can remain consistent when you control the hostname and migration path. Cloudinary has no self-host path. Your assets and transformation URLs live inside their platform. ## When Cloudinary is still the right call [#when-cloudinary-is-still-the-right-call] We would rather earn trust than oversell, so: if you need **video** transcoding at scale, a full **digital asset manager** with approval workflows, or AI features like auto-tagging and content-aware cropping *today*, Cloudinary's breadth is real and Keenpix does not match it. Keenpix is focused on optimizing images from origins you already operate, a published managed-delivery meter, and a self-host option. ## Related comparisons [#related-comparisons] * [Keenpix vs imgix](/compare/imgix-alternative) — current credit packages vs managed-delivery billing. * [Keenpix vs ImageKit](/blog/keenpix-vs-imagekit) — two delivery-pricing models, compared. * [How the Keenpix managed-delivery meter works](/blog/transparent-image-cdn-pricing) — including its cache boundary. * [Keenpix pricing](/pricing) — current allowances, overage rates, and billing behavior. ## Try it [#try-it] Point Keenpix at your existing S3, R2, or another supported origin and measure managed delivery on your own traffic before you commit. Or [self-host the engine](/docs/self-hosting) on infrastructure you operate. --- Markdown path: https://keenpix.com/blog/keenpix-vs-imagekit.md # ImageKit Bandwidth Pricing Explained: Limits and Overage > Learn how ImageKit bandwidth allowances and overage work, then compare their boundary with Keenpix managed-delivery pricing. Canonical HTML: [https://keenpix.com/blog/keenpix-vs-imagekit](https://keenpix.com/blog/keenpix-vs-imagekit) Author: Raed Bahri Published: 2026-07-02 Last reviewed: 2026-08-22 ImageKit publishes bandwidth-oriented plans rather than a pooled transformation-credit model. This guide explains its allowances, overage, and enforcement boundary. If you are choosing between products, use the sourced [Keenpix vs ImageKit decision guide](/compare/imagekit-alternative) for feature fit, migration steps, and cases where ImageKit is the better choice. ## Similar units, different meter boundaries [#similar-units-different-meter-boundaries] Both products publish delivery-oriented plans, but their boundaries are product-specific. Keenpix counts optimized bytes delivered through its managed Cloudflare edge or application once, while ImageKit counts optimized delivery through its own network. | | Keenpix | ImageKit | | --------------------- | ---------------------------------------------- | ----------------------------- | | Billing unit | Keenpix managed delivery (GB) | Bandwidth delivered (GB) | | Transformations | Unlimited, free | Included | | Overage rate | Founding $0.05–0.08/GB; standard $0.07–0.12/GB | $0.45–0.50/GB | | Entry price | $9/mo | Comparable-to-higher | | Self-host option | Yes (open source, AGPL) | No | | Paid overage behavior | Always on, billed at period end | Confirm current account terms | *ImageKit pricing re-verified August 22, 2026 against [ImageKit's current plans](https://imagekit.io/plans).* Keenpix charges $0.12/$0.09/$0.07 per delivered GB of overage on Basic/Pro/Business. Paid delivery continues and accumulated usage is billed at the end of the period. Compare the full workload and contract rather than treating one overage number as a universal lowest-price claim. ## Free transforms, honestly unlimited [#free-transforms-honestly-unlimited] Keenpix never charges for transformations. Resize, crop, format conversion (AVIF, WebP, JPEG, PNG, GIF, HEIF, TIFF, SVG), and quality controls are all free on every plan, so aggressive optimization never costs you anything. ## Open source is the real differentiator [#open-source-is-the-real-differentiator] ImageKit is a closed, managed platform. **The Keenpix cloud release publishes its image engine under AGPL-3.0.** You can run that version with Docker and pay no Keenpix license fee, while owning the infrastructure, updates, monitoring, and routing. The transform grammar can remain consistent during a move when you control the hostname and migration plan. That is the guarantee no proprietary vendor can offer: if the managed product ever stops making sense, you are not trapped. ## When ImageKit makes sense [#when-imagekit-makes-sense] ImageKit ships a media library and DAM-style features, and a mature global edge network. If you want an all-in-one managed media library with a UI for non-developers today, it is a strong choice. Keenpix is deliberately focused: **optimize and deliver images fast, price it transparently, and let you self-host.** ## Related comparisons [#related-comparisons] * [Keenpix vs Cloudinary](/compare/cloudinary-alternative) — credits vs managed-delivery pricing. * [Keenpix vs imgix](/compare/imgix-alternative) — current credit packages vs managed-delivery billing. * [How the Keenpix managed-delivery meter works](/blog/transparent-image-cdn-pricing) — including its cache boundary. * [Keenpix pricing](/pricing) — current plans and meter boundary. ## Compare on your own traffic [#compare-on-your-own-traffic] Point Keenpix at your existing origin and check the managed-delivery meter against your current ImageKit bill. A migration still requires URL or hostname routing work. Or [self-host with no Keenpix license fee](/docs/self-hosting) while operating and paying for the infrastructure yourself. --- Markdown path: https://keenpix.com/blog/keenpix-vs-imgix.md # imgix Credits Explained: Delivery and Storage Pricing > Learn how imgix credit packages combine managed media and delivery allowances, then compare the meter with Keenpix managed-delivery pricing. Canonical HTML: [https://keenpix.com/blog/keenpix-vs-imgix](https://keenpix.com/blog/keenpix-vs-imgix) Author: Raed Bahri Published: 2026-07-04 Last reviewed: 2026-08-31 imgix publishes a broad rendering API and uses credits across managed media, delivery, and feature-dependent transformations. This guide focuses on those billing mechanics. If you are actively selecting a vendor, use the sourced [Keenpix vs imgix decision guide](/compare/imgix-alternative) for feature fit, migration steps, and cases where imgix is the better choice. ## Two different meters [#two-different-meters] imgix now sells monthly credit packages. Its published Starter package includes 100 credits, up to 50 GB of managed media, and up to 100 GB of delivery; larger packages raise both allowances. Unused credits do not roll over. Keenpix meters **optimized bytes delivered through managed cloud** and does not meter the number of origin images, transformations, requests, or team members. Managed Cloudflare edge hits and application responses each count once. | | Keenpix | imgix | | ------------------- | ----------------------------- | ------------------------------------------------- | | Billing unit | Keenpix managed delivery (GB) | Credit package with media and delivery allowances | | Transformations | Unlimited, free | Credit use varies by feature | | Large image catalog | No origin-image count | Managed-media allowance applies | | Overage | One linear published rate | Plan-dependent | | Self-host option | Yes (open source, AGPL) | No | | Entry price | $9/mo | $25/mo for Starter (100 credits) | *Re-verified August 31, 2026 against imgix's [current credit packages and costs](https://www.imgix.com/faq-categories/purchasing-credits-and-costs), [pricing page](https://www.imgix.com/pricing), and [credit consumption guide](https://www.imgix.com/credit-consumption). Published monthly packages run from $25 Starter (100 credits) through $500 Growth Plus (3,570 credits), with $75 Basic, $150 Midrange, and $300 Growth between them. Annual packages are about 17% lower. Transformation credit use varies by feature, so model the operations you actually call.* ## Free, unlimited transforms [#free-unlimited-transforms] Both products give you a powerful URL-based transformation API — resize, crop, format conversion, quality, and more. The difference is the billing consequence: with Keenpix, **transformations never cost anything**, so building responsive `srcset` sets or adding modern AVIF/WebP variants has zero pricing impact. ## The self-host escape hatch [#the-self-host-escape-hatch] The Keenpix cloud release publishes its image engine under **AGPL-3.0**. You can run that version with Docker without a Keenpix license fee, while paying for and operating the infrastructure yourself. imgix is a managed platform; its service is not a self-hosted deployment path. For teams with compliance requirements, variable budgets, or a preference for owning their stack, that optionality matters. A move can preserve the transform grammar when you control the hostname and migration path, but it still requires infrastructure and routing work. ## When imgix is the better fit [#when-imgix-is-the-better-fit] imgix has years of edge-network maturity and a deep rendering feature set. If you are already invested in its API and its credit-package economics fit your media and delivery usage, there may be no reason to switch. Keenpix competes on a published managed-delivery meter, included transforms and teammates, and the self-host option — not on matching every feature of a mature incumbent on day one. ## Related comparisons [#related-comparisons] * [Keenpix vs Cloudinary](/compare/cloudinary-alternative) — pooled credits versus managed delivery. * [Keenpix vs ImageKit](/blog/keenpix-vs-imagekit) — two delivery-pricing models, compared. * [How the Keenpix managed-delivery meter works](/blog/transparent-image-cdn-pricing) — including its cache boundary. * [Keenpix pricing](/pricing) — current plan limits and application-meter details. ## See your real number [#see-your-real-number] The honest way to compare is on your own traffic. Point Keenpix at your existing origin and watch the managed-delivery meter — then decide. A migration still requires URL or hostname routing work. Or [self-host with no Keenpix license fee](/docs/self-hosting) while operating the infrastructure yourself. --- Markdown path: https://keenpix.com/blog/migrate-cloudinary-to-keenpix.md # Cloudinary to Keenpix Migration Guide > Migrate Cloudinary image URLs to Keenpix with an explicit parameter map, origin plan, canary rollout, cache checks, and tested rollback. Canonical HTML: [https://keenpix.com/blog/migrate-cloudinary-to-keenpix](https://keenpix.com/blog/migrate-cloudinary-to-keenpix) Author: Raed Bahri Published: 2026-08-23 Last reviewed: 2026-08-23 You can migrate Cloudinary image delivery to Keenpix without changing every image at once. Keep Cloudinary serving production, choose an origin Keenpix can fetch, translate only the transformations Keenpix actually supports, and canary the new URLs behind a reversible application flag. The first decision is asset ownership. If the originals already live in S3, R2, or a web origin you control, point Keenpix there. If the only copy is in Cloudinary's Media Library, either export it to storage you control or temporarily use the public, untransformed Cloudinary delivery URL as Keenpix's source. The temporary path still depends on Cloudinary and can incur Cloudinary delivery, so it is an overlap strategy, not a completed exit. This guide covers images. Keep Cloudinary when you depend on its video pipeline, DAM workflows, upload processing, generative features, face/object-aware crops, arbitrary transformation chains, or authenticated asset delivery and cannot replace those capabilities separately. ## Prerequisites: inventory before rewriting URLs [#prerequisites-inventory-before-rewriting-urls] Do not begin with a regular expression over production HTML. Build a migration manifest first: 1. List every Cloudinary cloud name, delivery hostname, asset type, delivery type, and URL-generating SDK or helper. 2. Separate `image/upload` assets from `image/fetch`, `private`, `authenticated`, video, raw files, and client-side upload flows. 3. Count transformation strings by traffic or page importance. Expand named transformations and record chained steps, variables, overlays, conditional transforms, and AI add-ons. 4. Identify the authoritative original for each asset. Record its versioned URL, content type, dimensions, and a checksum when you can retrieve the bytes. 5. Record security controls: Strict Transformations, signed delivery URLs, allowed fetch domains, token or cookie access, and custom delivery hostnames. 6. Choose a small, representative canary: fixed-width thumbnails, responsive content images, transparent PNGs, animated assets, and at least one expected failure. 7. Keep the existing Cloudinary URL generator deployable until the rollback window closes. Cloudinary's URL structure varies by asset and delivery type. Its current default form is `/////.`. Do not assume every URL matching `res.cloudinary.com` is a public uploaded image. ## Choose the source topology [#choose-the-source-topology] ### Originals already live on your origin [#originals-already-live-on-your-origin] This is the cleanest path. Keep the original URL stable, add its hostname under **Settings → Security → Allowed hosts**, and make Keenpix URLs from that absolute source. The project allowlist is checked before cache lookup; an empty list blocks every request. ```text Original: https://assets.example.com/catalog/bag-v17.jpg Managed Keenpix: https://cdn.keenpix.com/p/PROJECT_ID/img/https%3A%2F%2Fassets.example.com%2Fcatalog%2Fbag-v17.jpg?w=800&q=75&fmt=auto ``` ### Originals exist only in Cloudinary [#originals-exist-only-in-cloudinary] A public `image/upload` URL without a transformation component can be a temporary source: ```text https://res.cloudinary.com/CLOUD/image/upload/v1720000000/catalog/bag.jpg ``` Allowlist `res.cloudinary.com`, encode that complete URL as the Keenpix source, and retain the Cloudinary version segment. Test that the URL returns the original you expect; a format extension or upload-time transformation may mean it is not byte-identical to a separate master. This topology preserves a Cloudinary dependency. Keenpix cache misses fetch through Cloudinary, and Cloudinary remains the system that stores or delivers the original. Export to an owned origin before calling the provider migration complete. ### Private, authenticated, or header-protected originals [#private-authenticated-or-header-protected-originals] Stop and redesign this path before canarying. Keenpix's current public transform fetcher sends a normal HTTP(S) request with its own user agent; it does not accept arbitrary origin authorization headers or Cloudinary credentials. Cloudinary `/s--...--/` delivery signatures and Keenpix `sig=` signatures are different protocols and are not interchangeable. Provide a source URL Keenpix can fetch safely, such as a deliberately public asset host or a server-generated, stable source URL whose expiry exceeds the complete transform and cache-fill window. Do not make a private Cloudinary asset public merely to finish the migration. If authenticated media is a product requirement, Cloudinary may remain the correct delivery layer. ## Map URLs explicitly [#map-urls-explicitly] Start from the semantic transformation, not from punctuation. Cloudinary separates comma-delimited qualifiers and slash-delimited transformation stages; Keenpix has one query object and a fixed processing order. | Cloudinary URL concept | Keenpix equivalent | Migration note | | -------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `w_800` | `w=800` | Keenpix accepts 1–5000 and clamps out-of-range values. | | `h_600` | `h=600` | Verify the result together with `fit`; dimensions alone do not define crop behavior. | | `q_75` | `q=75` | Numeric values map, but encoder implementations can produce different bytes. Compare visually. | | `q_auto` | Project default quality or an explicit `q` | No exact policy mapping. Pick a measured numeric value or keep the Keenpix project default. | | `f_auto` | Omit `fmt` or use `fmt=auto` | Keenpix negotiates AVIF/WebP/JPEG only when project Auto-format is enabled. Codec choice need not match Cloudinary's. | | `f_webp`, `f_avif`, `f_jpg`, `f_png` | `fmt=webp`, `fmt=avif`, `fmt=jpeg`, `fmt=png` | Use explicit formats when an outer CDN cannot vary safely by `Accept`. | | `c_fill,w_800,h_600` | `w=800&h=600&fit=cover` | Both fill a box by cropping, but inspect crop and upscaling behavior. | | `c_fit` or `c_limit` | Usually `fit=inside` | Keenpix does not enlarge by default. Confirm both dimensions and transparent padding expectations. | | `c_pad` plus `b_*` | `fit=contain&background=...` | Check color parsing, alpha, and the final canvas dimensions. | | `c_scale` with unlike width and height | `fit=fill` | This may distort the image. Treat it as an intentional exception. | | `g_center`, `g_north`, `g_south_east` | `position=centre`, `position=north`, `position=southeast` | Keenpix accepts compass anchors; spelling and crop output still need visual checks. | | `g_auto`, `g_face`, object gravity | No exact equivalent | `position=attention` and `position=entropy` are libvips heuristics, not face/object-aware Cloudinary crops. Preserve Cloudinary or author crops manually. | | `dpr_2` | `dpr=2` | Keenpix supports 1–3. Do not also double the requested width in `srcset`. | | `e_blur:VALUE` | `blur=VALUE` | Numeric scales and processing order are not compatibility promises. Recalibrate against approved fixtures. | | `fl_no_overflow` | Default Keenpix behavior | Keenpix does not upscale unless `enlarge=1` is present. | | `t_NAME` | Expand and assess manually | Keenpix v0.3 has no named transformation presets. Unsupported steps block parity. | | overlays, text, variables, conditionals, AI, video | No general equivalent | Do not silently drop a watermark, entitlement mark, crop rule, or moderation result. | Cloudinary may treat two differently ordered transformation strings as separate derived transformations even when their visible result looks similar. Keenpix normalizes known parameters into its effective transform object. That difference can change cache cardinality, but it does not prove a lower bill or faster output; measure your own workload. ## Build a small, owned URL generator [#build-a-small-owned-url-generator] Avoid a generic Cloudinary parser that quietly ignores unknown steps. Translate approved presets in application code and fail closed for everything else: ```js const keenpixPresets = { productCard: { fit: 'cover', height: 600, quality: 75, width: 800 }, productThumb: { fit: 'cover', height: 240, quality: 75, width: 240 }, } export function keenpixImageUrl(source, presetName) { const preset = keenpixPresets[presetName] if (!preset) throw new Error(`Unmapped image preset: ${presetName}`) const params = new URLSearchParams({ fit: preset.fit, fmt: 'auto', h: String(preset.height), q: String(preset.quality), w: String(preset.width), }) return `https://cdn.keenpix.com/p/PROJECT_ID/img/${encodeURIComponent(source)}?${params}` } ``` If the source contains its own query string or fragment, encode the entire source as shown. Alternatively, use Keenpix's `/img/?url=...` form and let `URLSearchParams` encode it. Never concatenate an unescaped source query with transform parameters. ## Validate visual and protocol parity [#validate-visual-and-protocol-parity] Make expected differences explicit. A changed JPEG encoder, automatic-quality policy, crop heuristic, color profile, animation default, or SVG handling can alter bytes without being a defect. A removed watermark, wrong crop, lost animation, or changed entitlement is a defect. Use fixtures with approved reference images and inspect the response, not only the browser preview: ```bash curl -sS -D /tmp/keenpix-headers.txt \ -H 'Accept: image/avif,image/webp,image/*,*/*' \ -o /tmp/keenpix-output \ 'https://cdn.keenpix.com/p/PROJECT_ID/img/ENCODED_SOURCE?w=800&h=600&fit=cover&q=75&fmt=auto' cat /tmp/keenpix-headers.txt file /tmp/keenpix-output sha256sum /tmp/keenpix-output ``` Then exercise the security boundary: ```bash curl -sS -o /dev/null -w '%{http_code}\n' \ 'https://cdn.keenpix.com/p/PROJECT_ID/img/https%3A%2F%2Funapproved.example%2Fimage.jpg?w=800' # Expected: 403 ``` For every representative page, check intrinsic dimensions, transparent backgrounds, animation, orientation, crop focus, `Content-Type`, `Cache-Control`, and `Vary`. Compare transferred bytes and user-visible loading on your pages; do not borrow a compression percentage from another catalog. ## Check cache identity and origin security [#check-cache-identity-and-origin-security] Successful Keenpix transforms currently return `Cache-Control: public, max-age=31536000, immutable`. The cache key includes the project, complete source URL, and effective transform object. That makes source versioning part of correctness. * If an image can change in place, add a stable content version to its path or query before migration. Do not use a timestamp that changes on every request. * Cache the full Keenpix query string. Dropping `w`, `h`, `q`, or `fmt` from an outer cache key can serve the wrong image. * With `fmt=auto`, verify every outer cache separates variants by `Accept`; otherwise use explicit format URLs. * Keep the source allowlist as narrow as possible. Keenpix also blocks private/internal IPs, pins the resolved IP, and rechecks redirects, but the allowlist is still the project boundary. * Enable Keenpix HMAC URLs only when transform tampering or cache-busting is a real risk. Generate signatures on a server; a browser-visible Cloudinary API secret or Keenpix signing secret is a credential leak. * A valid signed URL can be replayed. Signing controls parameter tampering, not reader identity. Keenpix's current public API documents prewarming but not a public per-asset purge endpoint. Prefer versioned source URLs for changed content, and include any customer-owned CDN purge in the runbook. If you prewarm, do it from trusted server automation with a project-scoped API key, never from client code. ## Stage the rollout, canary, and rollback [#stage-the-rollout-canary-and-rollback] Use one reversible flag around the URL generator. The exact percentages and observation window depend on your traffic and risk; the important property is that the cohort is stable and rollback does not require another migration. 1. **Shadow validation:** generate both URLs in tests or logs without changing user responses. Reject every unmapped preset. 2. **Internal canary:** enable Keenpix for staff, preview deployments, or a fixed asset list. Exercise cold cache, warm cache, origin timeout, unsupported input, and denied origin cases. 3. **Production canary:** route a stable low-risk cohort to Keenpix. Do not randomize on every request, because that destroys cache comparability and makes failures hard to reproduce. 4. **Expand by content class:** move simple resize/quality URLs before crops, animation, SVG, or source topologies that still depend on Cloudinary. 5. **Hold overlap:** keep Cloudinary credentials, URLs, and account configuration intact until existing HTML, browser caches, crawlers, emails, and third-party embeds have aged through the agreed rollback window. 6. **Finish the asset exit separately:** if Cloudinary is the temporary origin, copy masters and metadata to the owned origin, checksum them, update only the source portion of the Keenpix URL, canary again, and retain the previous source version for rollback. Define rollback triggers before canarying: unexpected image 4xx/5xx, origin saturation, material crop or color mismatches, missing security marks, cache-variant mixing, or a user-facing loading regression on representative pages. Roll back by disabling the Keenpix flag and redeploying the existing Cloudinary generator. Keep Keenpix available long enough for old pages that still contain its URLs; a deploy cannot recall HTML already cached by clients. ## Honest incompatibilities and Cloudinary wins [#honest-incompatibilities-and-cloudinary-wins] Cloudinary is the better fit when the migration would require rebuilding a DAM, upload widget, video pipeline, moderation flow, enterprise support process, AI transformation, authenticated delivery system, or complex named/chained transformations. It is also lower risk when Cloudinary URLs are a public contract used by customers and you cannot keep a compatibility hostname. Keenpix is narrower: it fetches images from origins you operate or deliberately expose, transforms them through its documented query grammar, and can run as managed cloud or a self-hosted AGPL release. That narrower boundary is useful only if it matches the job you need done. ## Conversion bridge: prove one image class first [#conversion-bridge-prove-one-image-class-first] If the compatibility audit passes, create a Keenpix project, allowlist one non-sensitive origin, and migrate one named application preset—not the whole Cloudinary account. Use the [transform parameter reference](/docs/reference/parameters), [signed URL guide](/docs/concepts/signed-urls), and [responsive image checklist](/blog/responsive-image-cdn-guide) during the canary. Compare the architecture and vendor-fit tradeoffs in [Keenpix vs Cloudinary](/compare/cloudinary-alternative), then review [managed pricing](/pricing) or the [self-hosting path](/docs/self-hosting) with your measured traffic. ## Sources and verification [#sources-and-verification] Competitor behavior in this guide was re-verified on August 23, 2026 against Cloudinary's official [transformation URL reference](https://cloudinary.com/documentation/transformation_reference), [remote fetch documentation](https://cloudinary.com/documentation/fetch_remote_images), [named transformation behavior](https://cloudinary.com/documentation/named_transformations), [media access controls](https://cloudinary.com/documentation/control_access_to_media), and [migration guide](https://cloudinary.com/documentation/migration). Keenpix behavior is based on the current [endpoint](/docs/reference/endpoint), [parameters](/docs/reference/parameters), [caching](/docs/concepts/caching), and [security](/docs/concepts/security) documentation. Unknown account-specific Cloudinary contracts, add-ons, transformations, and origin access rules must be verified in your own environment. --- Markdown path: https://keenpix.com/blog/migrate-imagekit-to-keenpix.md # ImageKit to Keenpix Migration Guide > Move ImageKit transforms and origins to Keenpix with a compatibility table, asset-export gate, canary plan, cache checks, and rollback. Canonical HTML: [https://keenpix.com/blog/migrate-imagekit-to-keenpix](https://keenpix.com/blog/migrate-imagekit-to-keenpix) Author: Raed Bahri Published: 2026-08-23 Last reviewed: 2026-08-23 An ImageKit-to-Keenpix migration is straightforward only when your originals already live on an origin Keenpix can fetch and your URL grammar uses common image transforms. If assets live only in ImageKit's Media Library, move them to storage you control first. If you use ImageKit origin fallback, video, DAM workflows, named transformations, layers, AI operations, C2PA, or private-file delivery, treat those as separate compatibility projects—not query-string substitutions. The safe rollout keeps ImageKit online, creates an explicit transform map, points a stable canary cohort at Keenpix, and preserves the old URL generator until source checks, cache behavior, and rollback have been exercised in production-like conditions. ## Prerequisites: classify assets, origins, and transforms [#prerequisites-classify-assets-origins-and-transforms] Build a manifest before changing an endpoint: 1. Export or enumerate every ImageKit URL endpoint, attached origin, origin-preference order, custom domain, and Media Library path. 2. Identify the authoritative copy of each asset: your S3-compatible bucket, web server, ImageKit Media Library, or another attached origin. 3. Inventory transformation strings from both path syntax (`/tr:w-400,q-80/`) and query syntax (`?tr=w-400,q-80`). Expand every `n-NAME` named transformation. 4. Separate still images from video, adaptive streaming, overlays, AI transforms, generated media, and non-media files delivered through ImageKit. 5. Record security behavior: signed URLs, expiry, private files, restrictions on unnamed transforms, referrer/IP rules, and any origin credentials stored in ImageKit. 6. Record invalidation behavior: purge calls, wildcard purges, origin cache headers, version query parameters, and assets that overwrite the same path. 7. Choose representative fixtures and keep the ImageKit URL generator ready for rollback. Do not infer the source URL by removing `ik.imagekit.io` from an arbitrary request. An ImageKit URL endpoint can search the Media Library and multiple attached origins in order. Keenpix instead receives one absolute source URL per transform request. ## Decide whether the origin can move [#decide-whether-the-origin-can-move] ### Your own storage is already the source of truth [#your-own-storage-is-already-the-source-of-truth] Point Keenpix directly at the original asset URL and allowlist its hostname under **Settings → Security → Allowed hosts**: ```text ImageKit delivery: https://ik.imagekit.io/ACCOUNT/tr:w-800,q-75,f-auto/catalog/bag.jpg Owned origin: https://assets.example.com/catalog/bag.jpg?v=17 Managed Keenpix: https://cdn.keenpix.com/p/PROJECT_ID/img/https%3A%2F%2Fassets.example.com%2Fcatalog%2Fbag.jpg%3Fv%3D17?w=800&q=75&fmt=auto ``` Keep the source version inside the encoded URL. It becomes part of Keenpix's cache identity and gives you a deterministic way to publish changed bytes. ### Assets live only in ImageKit Media Library [#assets-live-only-in-imagekit-media-library] Keenpix has no asset library or upload store. Copy the originals, directory structure, required metadata, and any application identifiers to a durable origin you control. ImageKit's current upload documentation lists Rclone as a bulk migration option, but the exact command, supported metadata, and account permissions depend on your environment. Test an export sample before relying on it for the full library. For the sample and the final copy: * compare asset counts by path and content type; * checksum bytes where byte preservation matters; * verify Unicode, case-sensitive paths, spaces, and duplicate names; * preserve application-level IDs separately from provider file IDs; * confirm EXIF, color profiles, animation, and metadata requirements; * make the new origin readable by Keenpix without exposing credentials in public URLs. Do not close or delete ImageKit storage until old URLs, emails, feeds, browser caches, and third-party embeds have passed the rollback window. ### One ImageKit URL endpoint searches multiple origins [#one-imagekit-url-endpoint-searches-multiple-origins] There is no direct Keenpix equivalent to ImageKit's sequential origin preference. Choose the source in your application, consolidate the files behind one canonical origin, or build a controlled origin gateway that owns fallback behavior. A silent fallback can mask missing files during migration, so verify which origin actually served every canary asset. ### ImageKit holds private origin credentials [#imagekit-holds-private-origin-credentials] Keenpix's current transform fetcher does not accept arbitrary per-project authorization headers or ImageKit origin credentials. It fetches an explicit HTTP(S) source URL after host and SSRF checks. Provide a source it can legitimately read, or keep ImageKit for that protected path. Short-lived presigned source URLs can expire before a cold transform or revalidation workflow; do not adopt them without an expiry and cache-failure design. ## Map ImageKit transformation semantics [#map-imagekit-transformation-semantics] ImageKit supports path and query transformation syntax. Normalize both into semantic presets first; then generate Keenpix query parameters. Do not pass the `tr` string through unchanged. | ImageKit transform | Keenpix equivalent | Compatibility note | | --------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `w-400` | `w=400` | Both accept a width, but source caps and upscaling defaults can differ. | | `h-300` | `h=300` | Evaluate together with fit/crop behavior. | | `q-80` | `q=80` | Numeric quality is not byte-identical across encoders. Reapprove visual quality. | | `f-auto` | Omit `fmt` or use `fmt=auto` | Keenpix negotiates AVIF/WebP/JPEG when project Auto-format is enabled; the selected codec may differ. | | `f-webp`, `f-avif`, `f-jpg`, `f-png` | `fmt=webp`, `fmt=avif`, `fmt=jpeg`, `fmt=png` | Explicit format disables automatic negotiation for that URL. | | `dpr-2` | `dpr=2` | Keenpix supports 1–3; ImageKit documents a wider DPR range. Values outside Keenpix's range need a new width strategy. | | `cm-pad_resize` plus `bg-*` | Usually `fit=contain&background=...` | Confirm output canvas, alpha, and color syntax with fixtures. | | fixed `w` + `h` crop | Usually `fit=cover` | ImageKit crop mode and focus determine the real equivalent; dimensions alone are insufficient. | | `fo-left`, `fo-right`, compass focus | `position=left`, `position=right`, or a compass value | Check accepted Keenpix spelling and visual output. | | automatic/object/face focus | No exact equivalent | `position=attention` and `position=entropy` are libvips heuristics, not ImageKit smart or object-aware crops. | | `rt-90` | `rotate=90` | Validate orientation and processing order. | | `bl-10` | `blur=10` | Parameter names resemble each other, but numeric effect and ordering are not a parity guarantee. | | `e-grayscale` | `grayscale=1` | Compare color-profile handling and output format. | | `n-NAME` | Expand and assess manually | Keenpix v0.3 has no named transformation presets. Fail closed if an expanded step is unsupported. | | chained transforms | No general one-to-one map | Keenpix applies a documented fixed pipeline; order-dependent ImageKit chains require fixture-level review. | | image/text/video layers | No general equivalent | A watermark may be configured at the Keenpix project level, but that is not a generic layer parser. | | video, AI, conditional transforms, C2PA | No general equivalent | Keep ImageKit or replace each workflow with a separately verified system. | ImageKit's `w-auto`, arithmetic expressions, named transforms, and URL Endpoint Functions can encode application logic that a table cannot preserve. If a transform is not in the approved map, return an error during development and keep its production URL on ImageKit. ## Replace provider strings with application presets [#replace-provider-strings-with-application-presets] The migration boundary should be small enough to test. Keep source selection and visual intent in application-owned data: ```js const imagePresets = { card: { fit: 'cover', height: 600, quality: 75, width: 800 }, logo: { fit: 'inside', quality: 85, width: 320 }, } export function keenpixUrl(source, presetName) { const preset = imagePresets[presetName] if (!preset) throw new Error(`Unsupported image preset: ${presetName}`) const params = new URLSearchParams({ fit: preset.fit, fmt: 'auto', q: String(preset.quality), w: String(preset.width), }) if (preset.height) params.set('h', String(preset.height)) return `https://cdn.keenpix.com/p/PROJECT_ID/img/${encodeURIComponent(source)}?${params}` } ``` Do not convert `ik-s` and `ik-t` into Keenpix parameters. ImageKit and Keenpix sign different canonical messages with different secrets. When a Keenpix project requires signatures, generate `sig`, `kid`, and any configured `iat`/`exp` fields on a trusted server with the [Keenpix SDK signing helper](/docs/concepts/signed-urls). ## Cache migration is a content-version migration [#cache-migration-is-a-content-version-migration] ImageKit documents dashboard/API purging and recommends stable version query parameters for changed resources. Keenpix transform responses are currently `public, max-age=31536000, immutable`, and its cache key includes the complete source URL plus effective transform options. That creates a simple rule: **new source bytes need a new source URL**. * Preserve an existing stable `?v=17` value inside the encoded source URL. * If an asset is overwritten, advance the version once at publish time. Never use the current timestamp on each request. * Keep the full Keenpix transform query in every outer CDN cache key. * With `fmt=auto`, confirm the outer CDN honors the `Vary: Accept` boundary. If it cannot, emit explicit AVIF/WebP/JPEG sources. * Do not treat an ImageKit purge response as a Keenpix purge. They are independent caches. * Keenpix's current public API documents prewarming but does not document a public per-asset purge endpoint. Use versioned URLs as the primary update path and document any customer-owned CDN purge separately. Prewarm only bounded, real variants. Generating every possible width can fragment caches and load the origin without improving a page: ```bash curl -X POST 'https://keenpix.example.com/api/sdk/v1/projects/PROJECT_ID/prewarm' \ -H 'Authorization: Bearer PROJECT_SCOPED_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "sources": ["https://assets.example.com/catalog/bag.jpg?v=17"], "widths": [400, 800, 1200], "formats": ["avif", "webp"], "quality": 75, "fit": "cover" }' ``` The API key belongs in trusted automation, never in a frontend bundle. ## Verify origin safety and response behavior [#verify-origin-safety-and-response-behavior] Keenpix checks the allowed source host before its transform cache, blocks private/internal IP ranges, pins DNS resolution into the connection, and revalidates redirect hops. An allowlisted hostname is not permission to proxy any host it redirects to. Verify the happy path with more than one `Accept` value: ```bash curl -sS -D /tmp/avif.headers -o /tmp/avif.image \ -H 'Accept: image/avif,image/webp,image/*,*/*' \ 'https://cdn.keenpix.com/p/PROJECT_ID/img/ENCODED_SOURCE?w=800&q=75&fmt=auto' curl -sS -D /tmp/jpeg.headers -o /tmp/jpeg.image \ -H 'Accept: image/jpeg,*/*' \ 'https://cdn.keenpix.com/p/PROJECT_ID/img/ENCODED_SOURCE?w=800&q=75&fmt=auto' file /tmp/avif.image /tmp/jpeg.image ``` Check status, `Content-Type`, `Cache-Control`, `Vary`, pixel dimensions, alpha, orientation, animation, and decoded appearance. Repeat an identical request and inspect the Keenpix request logs or your outer CDN to confirm reuse at the layer you intend to operate. Verify denial as a release gate: ```bash curl -sS -o /dev/null -w '%{http_code}\n' \ 'https://cdn.keenpix.com/p/PROJECT_ID/img/https%3A%2F%2Funapproved.example%2Fphoto.jpg?w=400' # Expected: 403 ``` Also test an absent object, slow origin, redirect, oversized input, and unsupported format. Keenpix currently documents `502` for origin failure, `504` for origin timeout, and `413` for an over-size origin. The current HTTP transform path has no global concurrency semaphore or bounded queue, so it does not provide a built-in `503` overload response. If you need overload shedding, add admission control at your reverse proxy or another operator-owned edge and test its thresholds and retry behavior. Separately, `/api/health` returns `503` when a required dependency is unhealthy or the instance is draining; that health response is not a transform overload response. Your UI needs an explicit fallback or error state; a provider change does not create one automatically. ## Canary by origin and preset, not random request [#canary-by-origin-and-preset-not-random-request] Use a stable feature flag or routing rule: 1. **Export rehearsal:** copy a representative Media Library folder or verify a representative owned-origin folder. Prove counts, paths, and byte integrity before rewriting URLs. 2. **Shadow URL generation:** generate ImageKit and Keenpix URLs together in tests or non-user-visible diagnostics. Fail on every unrecognized transform. 3. **Internal canary:** route staff or preview traffic for one simple preset to Keenpix. Test cold/warm cache, denied hosts, origin failure, animation, and format fallbacks. 4. **Production canary:** choose a deterministic page, tenant, asset list, or user cohort. Random per-request switching makes cache and error comparisons noisy. 5. **Expand by risk:** common width/quality formats first; then crop/pad/rotation; keep smart crops, named chains, layers, private files, and video on ImageKit until they have an owned replacement. 6. **Hold provider overlap:** preserve ImageKit endpoints, credentials, custom domains, purge automation, and originals through the agreed rollback period. Define rollback triggers before enabling production traffic: unexpected image 4xx/5xx, missing objects after origin consolidation, visible crop/color/alpha mismatch, source saturation, incorrect format mixing, lost security controls, or a page-level loading regression on your representative routes. Rollback is the old URL generator plus the original source topology. Disable the Keenpix flag and deploy. Keep Keenpix serving during the overlap because already-rendered pages, browser caches, and external consumers may still request its URLs. If files were copied out of ImageKit, do not delete the new copy during rollback; deletion makes later reconciliation harder. ## Honest incompatibilities and ImageKit wins [#honest-incompatibilities-and-imagekit-wins] ImageKit remains the better fit when you need a browsable DAM, upload widgets, Media Library collaboration, video transformation or streaming, origin preference, advanced URL rewriting/functions, private-file semantics, named-transform restrictions, AI operations, layers, C2PA, or a mature unified media platform. Its free managed tier can also be the better economic choice for a workload that stays within the current allowance and enforcement rules. Keenpix is an image transform and delivery layer for origins you operate. It does not replace those media-management features, and moving to it adds a new service boundary even if your application continues to use the same framework and storage. ## Conversion bridge: test the owned-origin path [#conversion-bridge-test-the-owned-origin-path] If your manifest shows common image transforms and an origin you control, create one Keenpix project and migrate one application preset. Use the [Keenpix endpoint](/docs/reference/endpoint), [parameter reference](/docs/reference/parameters), and [cache guide](/docs/concepts/caching) to review the generated request. Read [Keenpix vs ImageKit](/compare/imagekit-alternative) for the broader vendor decision, compare the related [Cloudinary migration](/blog/migrate-cloudinary-to-keenpix) when origins overlap, and evaluate [managed pricing](/pricing) or [self-hosting](/docs/self-hosting) only with your measured delivery and operating costs. ## Sources and verification [#sources-and-verification] Competitor details were re-verified on August 23, 2026 against ImageKit's official [transformation URL guide](https://imagekit.io/docs/transformations), [resize and crop reference](https://imagekit.io/docs/image-resize-and-crop), [external storage and URL endpoint documentation](https://imagekit.io/docs/integration/connect-external-storage), [basic security features](https://imagekit.io/docs/media-delivery-basic-security), [CDN caching and purging guide](https://imagekit.io/docs/cdn-caching-and-purging), and [asset upload/export options](https://imagekit.io/docs/dam/upload-assets). Keenpix behavior is based on the current [parameters](/docs/reference/parameters), [responses](/docs/reference/responses), [security](/docs/concepts/security), and [SDK API](/docs/reference/sdk-api). Account-specific ImageKit origin functions, transformations, DAM metadata, contracts, and export behavior remain unknown until verified in the migrating account. --- Markdown path: https://keenpix.com/blog/migrate-vercel-image-optimization-to-keenpix.md # Vercel Image Optimization to Keenpix Migration > Move next/image from Vercel's optimizer to a Keenpix custom loader with config mapping, canary rollout, cache and origin checks, and rollback. Canonical HTML: [https://keenpix.com/blog/migrate-vercel-image-optimization-to-keenpix](https://keenpix.com/blog/migrate-vercel-image-optimization-to-keenpix) Author: Raed Bahri Published: 2026-08-23 Last reviewed: 2026-08-23 Do not rewrite cached `/_next/image` URLs in place. Keep `next/image`, add a Keenpix custom loader, canary it on selected components with the per-image `loader` prop, and only then make it the application-wide `loaderFile`. Next.js continues to generate responsive `srcset` candidates; Keenpix replaces the service that fetches and transforms each candidate. Your application can stay on Vercel. Deployments, routing, previews, functions, and pages do not need to move. This change is only appropriate when the value of a separate image-delivery layer justifies another hostname, origin policy, cache, failure mode, and vendor relationship. Keep Vercel Image Optimization when its integrated behavior already meets your requirements. It can be the simpler or cheaper choice, especially if you already pay for a plan, rely on Vercel's source-image cache purge, want one support boundary, or do not want a separate image service. ## Prerequisites: record the current Next.js contract [#prerequisites-record-the-current-nextjs-contract] Before adding a loader, inventory the parts of `next/image` that influence generated URLs and user-visible behavior: 1. Record the Next.js version and read its matching Image documentation. Next.js 16 requires an `images.qualities` allowlist. 2. Inventory every ``, per-image `loader`, `unoptimized`, `overrideSrc`, static import, remote URL, data URL, fill layout, placeholder, priority/preload, and `sizes` value. 3. Save the current `images.remotePatterns`, `localPatterns`, `deviceSizes`, `imageSizes`, `qualities`, `formats`, `minimumCacheTTL`, and custom `path` configuration. 4. Identify the real source behind every relative `src`. A Keenpix loader needs an absolute HTTP(S) URL that its servers can fetch. 5. Separate public assets from sources that require cookies, bearer tokens, or request headers. The default Next.js optimizer does not forward source-request headers, and Keenpix's current transform fetcher does not accept arbitrary origin auth headers either. 6. Capture rendered `` markup and network requests on representative desktop and mobile pages. Include the likely LCP image, responsive cards, transparent assets, static imports, animation, and one expected failure. 7. Keep the current Next.js configuration in version control so rollback is one deploy, not a manual dashboard reconstruction. The browser usually sees a Vercel/Next URL shaped like this: ```text /_next/image?url=%2Fproducts%2Fbag.jpg&w=828&q=75 ``` That URL is an output of the default loader, not a durable source URL. The migration input is the component's original `src`, width, and quality. ## Understand what stays and what moves [#understand-what-stays-and-what-moves] Next.js still owns component layout and candidate generation: * `width`, `height`, and `fill` reserve layout space; * `sizes` tells the browser how wide the slot will be; * `deviceSizes` and `imageSizes` bound the generated width candidates; * `qualities` bounds allowed quality values; * lazy loading, preload/fetch priority, placeholders, alt text, and `overrideSrc` remain component concerns. The custom loader receives `{ src, width, quality }` and returns a URL. Keenpix then owns source fetching, format negotiation, transformation, and its caches. A loader does not automatically copy Vercel or Next.js origin restrictions into Keenpix. ## Map the configuration deliberately [#map-the-configuration-deliberately] | Next.js / Vercel behavior | Keenpix migration | Compatibility note | | --------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/_next/image?url=...&w=828&q=75` | `/img/ENCODED_ABSOLUTE_SOURCE?w=828&q=75&fmt=auto` | Build from the original `src`; do not wrap the old optimizer URL. | | `deviceSizes` and `imageSizes` | Keep them in `next.config` | Next.js still chooses candidate widths. Remove redundant widths only after inspecting real layouts and byte deltas. | | `qualities` | Keep the allowlist; pass the selected quality as `q` | Keenpix accepts 30–100. Values outside that range or Next's allowlist need an explicit policy. | | `images.formats` / `Accept` negotiation | Keenpix project Auto-format plus `fmt=auto` | Codec selection and output bytes need not match Vercel. Test AVIF, WebP, and JPEG fallback. | | `remotePatterns` | Add required source hosts to Keenpix Allowed hosts | Not equivalent: Next patterns can restrict protocol, port, pathname, and search; Keenpix's project boundary is hostname-based. Preserve narrower path policy in application code or signing. | | `localPatterns` | Resolve the relative path against a stable public origin | Keenpix has no equivalent local-path allowlist. Do not expose unintended application paths through a broad origin. | | static image import | Keep the import and let Next pass its emitted path to the loader | Confirm the absolute origin serves the hashed asset in production and previews. Intrinsic dimensions and generated blur data can remain Next features. | | `minimumCacheTTL` | No loader setting maps directly | Keenpix returns one-year immutable responses. Version source URLs when bytes change. | | Vercel source-image purge | No direct documented Keenpix public equivalent | Keenpix's current public API documents prewarm, not per-asset purge. Version the source and handle any outer-CDN purge separately. | | `unoptimized` | Keep it for assets that should bypass the loader | Test the emitted `src`; it may point directly at the application origin. | | `overrideSrc` | Keep it when you need a stable `src` while `srcset` uses optimized candidates | Inspect rendered HTML and image-search requirements. Do not assume it rewrites every candidate. | | authenticated remote source | No automatic equivalent | Neither default Next optimization nor Keenpix forwards arbitrary auth headers. Use an intentional public/gateway source or retain the existing protected path. | Vercel documents a cache key based on the source, width, quality, and normalized `Accept` value, with distinct behavior for local content hashes. Keenpix keys its transform cache by project, complete source URL, and effective transform object. Those models are similar enough to migrate responsive candidates, but they are not cache-compatible. ## Add the Keenpix loader [#add-the-keenpix-loader] Create `keenpix-loader.js` at the Next.js project root: ```js 'use client' export default function keenpixLoader({ src, width, quality }) { const baseUrl = process.env.NEXT_PUBLIC_KEENPIX_BASE_URL const project = process.env.NEXT_PUBLIC_KEENPIX_PROJECT const origin = process.env.NEXT_PUBLIC_IMAGE_ORIGIN if (!baseUrl || !origin) { throw new Error('Keenpix image environment variables are missing') } const source = src.startsWith('http') ? src : new URL(src, origin).href const params = new URLSearchParams({ fmt: 'auto', w: String(width) }) if (project) params.set('project', project) if (quality) params.set('q', String(quality)) return `${baseUrl.replace(/\/$/, '')}/img/${encodeURIComponent(source)}?${params}` } ``` For managed Keenpix, put the public project id in the delivery path: ```dotenv NEXT_PUBLIC_KEENPIX_BASE_URL=https://cdn.keenpix.com/p/YOUR_PROJECT_ID NEXT_PUBLIC_IMAGE_ORIGIN=https://www.example.com ``` For self-hosting, use your image host as the base and keep the project in the query: ```dotenv NEXT_PUBLIC_KEENPIX_BASE_URL=https://images.example.com NEXT_PUBLIC_KEENPIX_PROJECT=YOUR_PROJECT_ID NEXT_PUBLIC_IMAGE_ORIGIN=https://www.example.com ``` These values describe public routing and may be browser-visible. A signing secret is different: never prefix it with `NEXT_PUBLIC_` or put it in this loader. If the project requires signed URLs, generate the URL on a server or build step with the [Keenpix signing contract](/docs/concepts/signed-urls). Add every legitimate absolute source host to the Keenpix project allowlist. If relative sources resolve to `www.example.com`, allowlist that hostname—not the Vercel optimizer hostname. Never set the source to `/_next/image`; that would put one optimizer behind another and preserve the old billing/cache boundary. ## Canary before setting a global loader [#canary-before-setting-a-global-loader] Next.js supports a `loader` prop on individual images. Use it to migrate a small, representative component while the rest of the application keeps the default optimizer: ```jsx import Image from 'next/image' import keenpixLoader from '../../keenpix-loader' export function ProductCard({ product }) { return ( {product.name} ) } ``` This is a better canary than randomly choosing a provider inside the loader. A stable component or route creates reproducible URLs and lets both caches warm normally. After the canary passes, configure the application-wide loader: ```js // next.config.js module.exports = { images: { loader: 'custom', loaderFile: './keenpix-loader.js', qualities: [60, 75, 85], }, } ``` Preserve your existing `deviceSizes`, `imageSizes`, and any other still-relevant Image configuration. Do not copy the example quality list unless those values already match your components and visual review. ## Handle production, preview, and local origins [#handle-production-preview-and-local-origins] A relative Next.js `src` is easy for the default optimizer because it runs with the application. A separate image service needs an absolute origin it can reach. * Use a stable production asset hostname when possible. It avoids coupling image identity to deployment URLs. * Decide what preview builds should display. Pointing every preview at the production origin is stable but cannot preview newly added local assets. Pointing at an ephemeral preview hostname changes cache identity and may require allowlist updates. * Do not use `localhost` as a remotely fetched origin. Keenpix blocks loopback and private/internal addresses by design. * Ensure the public origin path returns the original file, not an HTML error page, auth redirect, or another optimizer URL. * If a source includes a token query, encode the complete source. Confirm token lifetime, cache exposure, and logs before using it; a short-lived source can fail on a later cold request. The current Keenpix fetcher follows only validated redirects and rejects redirects to private or unapproved hosts. Test your application's static-asset redirects and canonical hostname before launch. ## Verify rendered HTML and the network [#verify-rendered-html-and-the-network] Run the Next.js build and open representative pages at desktop and mobile widths. Inspect the final `` rather than the React props: 1. Confirm `srcset` contains multiple Keenpix URLs with different `w` values. 2. Confirm `sizes` matches the actual rendered slot and the browser selects a sensible candidate. 3. Check that relative, remote, and static-import sources resolve to the intended absolute origin. 4. Verify `unoptimized` and `overrideSrc` cases separately. 5. Check that the LCP image retains the intended eager/preload/fetch-priority behavior. 6. Confirm alt text, intrinsic dimensions, and placeholder behavior did not change. Check the transform response with explicit `Accept` headers: ```bash curl -sS -D /tmp/keenpix-avif.headers -o /tmp/keenpix-avif.image \ -H 'Accept: image/avif,image/webp,image/*,*/*' \ 'https://cdn.keenpix.com/p/PROJECT_ID/img/ENCODED_SOURCE?w=828&q=75&fmt=auto' curl -sS -D /tmp/keenpix-jpeg.headers -o /tmp/keenpix-jpeg.image \ -H 'Accept: image/jpeg,*/*' \ 'https://cdn.keenpix.com/p/PROJECT_ID/img/ENCODED_SOURCE?w=828&q=75&fmt=auto' file /tmp/keenpix-avif.image /tmp/keenpix-jpeg.image ``` Review HTTP status, `Content-Type`, `Cache-Control`, and `Vary`. Keenpix currently returns one-year immutable transform responses and varies automatic format by `Accept`. Every cache in front must keep those format variants separate; otherwise use explicit formats in `` sources. Verify the deny path: ```bash curl -sS -o /dev/null -w '%{http_code}\n' \ 'https://cdn.keenpix.com/p/PROJECT_ID/img/https%3A%2F%2Funapproved.example%2Fphoto.jpg?w=828&q=75' # Expected: 403 ``` Test a missing origin object, an origin timeout, a source larger than the configured cap, and a saturated transform service. Your component needs an appropriate fallback or error design; neither Next.js nor Keenpix can infer the product experience you want during an image outage. ## Stage rollout and rollback [#stage-rollout-and-rollback] Use gates that can be reversed independently: 1. **Baseline:** save Vercel request URLs, rendered candidates, visual fixtures, page-level loading measurements, and current image-usage data for the same routes and period. 2. **Per-image canary:** use the `loader` prop on one low-risk but representative component. Exercise preview and production-like origins. 3. **Route or content-class expansion:** move responsive cards, then content images, then the LCP path only after its own mobile/desktop review. 4. **Global configuration:** set `loaderFile` after every exceptional `Image` usage has an explicit disposition. 5. **Overlap:** keep Keenpix and Vercel image paths available. Old pages and browser caches can request either service after a deploy. 6. **Operational handoff:** document source versioning, allowlist ownership, signing, outer-CDN cache keys, incident fallback, and which team watches each provider. Choose a deterministic cohort and a risk-appropriate observation window; one universal percentage or duration would be false precision. Define rollback triggers first: unexpected image errors, wrong sources in previews, format mixing, visible quality/crop changes, origin load, cache fragmentation, or a representative mobile/desktop loading regression. For a per-image canary, rollback removes the `loader` prop. After global cutover, revert `images.loader` and `loaderFile`, redeploy, and keep Keenpix active for existing documents that still contain Keenpix URLs. Rolling back the app does not recall already-rendered HTML or purge browser caches. ## Cache and invalidation checks [#cache-and-invalidation-checks] Vercel added source-image cache invalidation for teams on its newer image-optimization pricing path in November 2025. If that purge workflow is important to your publishing system, record it as a Vercel win: Keenpix's current public API documents prewarming but not an equivalent public per-source purge. For Keenpix: * use a content hash or stable version on any source that can change; * keep the complete transform query in the CDN cache key; * avoid arbitrary width and quality values outside Next's bounded lists; * do not add per-request tracking parameters to image URLs; * verify `Vary: Accept` handling for `fmt=auto`; * purge a customer-owned outer CDN when your runbook requires it; * rotate signing secrets as a credential change plus outer-cache purge, not as revocation of bytes already in a browser. Prewarming is optional. Use it only for known widths and formats on critical assets, from trusted server automation. A large speculative matrix creates origin and cache work without proving a user benefit. ## Honest incompatibilities and Vercel wins [#honest-incompatibilities-and-vercel-wins] Vercel Image Optimization remains the stronger choice when you want one integrated deployment and support boundary, its cache invalidation workflow, framework-native defaults with no extra service, or an incremental cost that is lower than a separate managed plan for your actual usage. It also avoids absolute-origin and preview-host planning for local assets. Keenpix can be a fit when you deliberately want image delivery separated from the application host, one URL grammar across frameworks, its managed-delivery model, or a future move between managed and self-hosted Keenpix. It is not universally faster or cheaper. Compare transformations, cache reads and writes, data transfer, delivered bytes, origin traffic, failure behavior, and engineering time from the same workload. ## Conversion bridge: migrate one component [#conversion-bridge-migrate-one-component] Start with the maintained [Next.js integration guide](/docs/frameworks/nextjs) and the focused [custom-loader implementation article](/blog/nextjs-custom-image-cdn-loader). Add a Keenpix project, allowlist one public origin, and canary one component with the per-image loader. Review the broader [Keenpix vs Vercel Image Optimization](/compare/vercel-image-optimization-alternative) decision page, then compare [managed pricing](/pricing) or [self-hosting](/docs/self-hosting) only after you have your own candidate counts, cache behavior, delivered bytes, and operational requirements. ## Sources and verification [#sources-and-verification] Competitor and framework behavior was re-verified on August 23, 2026 against the official [Next.js Image component reference](https://nextjs.org/docs/app/api-reference/components/image), including custom loader, width, quality, `sizes`, and `overrideSrc` behavior; Vercel's [Image Optimization architecture and URL format](https://vercel.com/docs/image-optimization); [usage-management guidance](https://vercel.com/docs/image-optimization/managing-image-optimization-costs); and [source-image cache invalidation announcement](https://vercel.com/changelog/you-can-now-invalidate-the-cdn-cache-by-providing-a-source-image). Keenpix behavior is based on the current [Next.js guide](/docs/frameworks/nextjs), [endpoint](/docs/reference/endpoint), [caching](/docs/concepts/caching), and [security](/docs/concepts/security). Vercel account pricing mode, negotiated terms, traffic, and application-specific cache behavior remain unknown until checked in the migrating account. --- Markdown path: https://keenpix.com/blog/nextjs-custom-image-cdn-loader.md # How to optimize Next.js images with a custom CDN loader > Connect next/image to Keenpix with a custom loader, responsive sizes, format negotiation, and an origin allowlist you can verify in the browser. Canonical HTML: [https://keenpix.com/blog/nextjs-custom-image-cdn-loader](https://keenpix.com/blog/nextjs-custom-image-cdn-loader) Author: Raed Bahri Published: 2026-08-15 Last reviewed: 2026-08-15 `next/image` does not need to use the Next.js optimization endpoint. A custom loader keeps the component and its responsive `srcset`, but changes the URL behind each candidate. That is the useful seam for an external image CDN. The setup below sends each width selected by Next.js to Keenpix. It does not hide the source URL, move your originals, or put a signing secret in browser code. ## Add one loader for the application [#add-one-loader-for-the-application] Create `keenpix-loader.js` at the project root: ```js 'use client' export default function keenpixLoader({ src, width, quality }) { const baseUrl = process.env.NEXT_PUBLIC_KEENPIX_BASE_URL const project = process.env.NEXT_PUBLIC_KEENPIX_PROJECT const origin = process.env.NEXT_PUBLIC_IMAGE_ORIGIN if (!baseUrl || !origin) { throw new Error('Keenpix image environment variables are missing') } const source = src.startsWith('http') ? src : new URL(src, origin).href const params = new URLSearchParams({ fmt: 'auto', w: String(width), }) if (project) { params.set('project', project) } if (quality) { params.set('q', String(quality)) } return `${baseUrl}/img/${encodeURIComponent(source)}?${params.toString()}` } ``` For managed Keenpix, put the project id in the canonical delivery path: ```dotenv NEXT_PUBLIC_KEENPIX_BASE_URL=https://cdn.keenpix.com/p/your-project-id NEXT_PUBLIC_IMAGE_ORIGIN=https://assets.example.com ``` For self-hosted Keenpix, the base URL is your image host and the project remains a query parameter: ```dotenv NEXT_PUBLIC_KEENPIX_BASE_URL=https://images.example.com NEXT_PUBLIC_KEENPIX_PROJECT=your-project-id NEXT_PUBLIC_IMAGE_ORIGIN=https://assets.example.com ``` These public variables describe routing, not credentials. Add `assets.example.com` to the project's source-host allowlist. Keenpix rejects a source host that is not on that list. ## Tell Next.js to use it [#tell-nextjs-to-use-it] Configure the loader once: ```js // next.config.js module.exports = { images: { loader: 'custom', loaderFile: './keenpix-loader.js', }, } ``` The file path is relative to the project root. Next.js expects its default export to return a URL string. Your components still use `Image`: ```jsx import Image from 'next/image' export function ProductHero() { return ( Canvas backpack in olive green ) } ``` `sizes` deserves attention. Next.js uses it when building the width candidates, and the browser uses it when choosing one of those candidates. If a responsive image has no useful `sizes` value, the browser may assume a much wider layout and download more pixels than the slot needs. ## What `fmt=auto` changes [#what-fmtauto-changes] The loader asks Keenpix for `fmt=auto`. Keenpix checks the request's `Accept` header and selects AVIF, WebP, or JPEG. The response varies on that header, so every cache in front of the image service must respect `Vary: Accept`. The [AVIF and WebP production guide](/blog/avif-vs-webp-production-caching) shows how to test those variants without trusting the response header alone. Width and quality are part of the transformation URL. A 640-pixel candidate and a 1200-pixel candidate are separate cache variants. This is expected. Random query parameters are not: they create needless cache keys and should be rejected or covered by a signature when abuse is a concern. ## Check the result in a browser [#check-the-result-in-a-browser] Do not stop when the image appears. Inspect the rendered `` and the selected image request: 1. Confirm that `srcset` contains multiple Keenpix URLs with different `w` values. 2. Resize the viewport and check which candidate the browser selects. 3. Inspect `Content-Type`, `Vary`, and `Cache-Control` on the image response. 4. Verify that an unapproved source host fails instead of being fetched. Measure transferred bytes with your own source image and settings. A single percentage copied from another image says nothing reliable about your catalog. ## Signed URLs need a server boundary [#signed-urls-need-a-server-boundary] The global loader runs in browser-facing code. Do not place a Keenpix signing secret in it. If the project requires signed transform URLs, generate them on a server you control or route image requests through a server-side signing endpoint. The public project id and the signing secret are not interchangeable. For many public sites, the source-host allowlist is enough. Add signatures when third parties can create expensive variants or append parameters that manufacture cache misses. A signature does not bind a valid image URL to one referring site, and it does not prevent someone from copying and replaying that URL. ## When this setup is the wrong fit [#when-this-setup-is-the-wrong-fit] Keep the built-in Next.js optimizer when it already meets your deployment and cost requirements. A custom CDN adds another service boundary, cache policy, and failure mode. It earns that complexity when you need one image URL grammar across frameworks, want a separately managed delivery layer, or plan to move the same workload between managed and self-hosted Keenpix. ## Sources and verification [#sources-and-verification] This guide was checked on August 15, 2026 against the [current Next.js Image documentation](https://nextjs.org/docs/app/api-reference/components/image), the [Next.js custom loader configuration](https://nextjs.org/docs/pages/api-reference/config/next-config-js/images), and the [Keenpix Next.js guide](/docs/frameworks/nextjs). The example describes request behavior; it is not a performance benchmark. --- Markdown path: https://keenpix.com/blog/private-image-origins-security-boundaries.md # Private image origin security boundaries > Choose between public assets, signed source grants, authenticated gateways, and private networks without leaking origin credentials into delivery URLs. Canonical HTML: [https://keenpix.com/blog/private-image-origins-security-boundaries](https://keenpix.com/blog/private-image-origins-security-boundaries) Author: Raed Bahri Published: 2026-09-02 Last reviewed: 2026-09-02 A private image origin needs two independent decisions: how the transformer is authorized to fetch the source, and how viewers are authorized to receive a derived image. One signed URL does not automatically solve both. Keep origin credentials out of browser-visible URLs, logs, cache keys, analytics, and support records. ## Classify the asset before choosing a pattern [#classify-the-asset-before-choosing-a-pattern] | Asset class | Typical policy | Delivery implication | | ----------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------- | | Public marketing asset | Anyone may view; origin protection is operational | Public cached delivery is appropriate. | | Unlisted but not confidential | Discovery should be limited; disclosure is tolerable | Unguessable or signed delivery may help, but is not strong confidentiality. | | Account-private image | Only an authorized user or role may view | Authorization must happen before delivery; shared public caches need care. | | Time-limited download | Access expires or is revocable | Expiry, cache lifetime, and stale serving must align. | | Regulated or highly sensitive media | Strict access, audit, residency, deletion, and incident requirements | A general public image CDN may be the wrong system. | Write the classification and retention rule down. If the answer is “private” only because the bucket currently returns 403, the product policy is incomplete. ## Four origin access patterns [#four-origin-access-patterns] ### 1. Public origin with a delivery allowlist [#1-public-origin-with-a-delivery-allowlist] The transformer fetches a public source, but only from configured hosts. This is simple and cache-friendly. It protects the transformer from becoming an arbitrary proxy, not the source from direct access. ### 2. Short-lived presigned source URL [#2-short-lived-presigned-source-url] The application generates a time-limited source grant and gives it to the transformer. This can work for immediate transformations, but expiry interacts with retries, cold cache fills, revalidation, and purge recovery. If the signature is embedded in the transform URL, it may fragment caches and appear in logs or referrers. Prefer a design where the public delivery identity is stable and the source grant is resolved server-side. If that is unavailable, set the source grant longer than the complete transform and recovery window, restrict its scope, redact it everywhere, and test after expiry. ### 3. Authenticated origin gateway [#3-authenticated-origin-gateway] A gateway accepts a stable asset identifier from the transformer, authorizes the transform service through a private credential or mutually authenticated channel, and fetches the object. This keeps storage grants out of public URLs and centralizes policy, but the gateway becomes a production dependency that needs scaling, logging, rotation, and incident response. ### 4. Private network or service binding [#4-private-network-or-service-binding] The transformer reaches storage through a private network, workload identity, or platform binding. This offers a strong infrastructure boundary when both systems share an environment. It also reduces portability and may not be available from a managed third-party transformer. ## Viewer authorization is a separate layer [#viewer-authorization-is-a-separate-layer] A delivery signature should cover the normalized path and every parameter that can change output. Define: * signature algorithm and key version; * canonical parameter order and encoding; * expiry and clock-skew handling; * replay policy; * whether the signature is part of the cache key; * behavior after a key rotation or access revocation; * which layer returns 401, 403, and 404. Do not put a long-lived administrative API key in image markup. Do not rely on the `Referer` header as authorization; it may be absent, altered, or leaked. Cookie-based viewer authorization can work at an application gateway, but shared CDN caching must not serve an authorized response to another user. Use a private cache key or disable shared caching for user-specific bytes. ## Cache and revocation rules [#cache-and-revocation-rules] The maximum time a revoked image may remain available is not just the signed URL expiry. It can include edge freshness, browser freshness, stale serving, offline caches, and already-downloaded copies. ```text effective exposure window = max( viewer grant lifetime, shared cache freshness + allowed stale window, browser cache lifetime ) ``` That is a planning model, not a guarantee that downloaded data can be recalled. If immediate revocation is a requirement, public immutable caching is incompatible with the requirement. ## Defend the fetch boundary [#defend-the-fetch-boundary] Every source and redirect target should pass the same checks: 1. Parse with a standards-compliant URL parser. 2. Permit only required schemes, normally HTTPS. 3. Match an explicit hostname policy after normalization. 4. Resolve DNS and reject loopback, link-local, private, multicast, and metadata-service addresses. 5. Recheck each redirect and bound the hop count. 6. Limit connect time, total time, response bytes, decoded pixels, frames, and memory. 7. Validate content from bytes, not only the supplied extension or `Content-Type`. 8. Log the decision and safe identifiers without secrets. The [secure image pipeline guide](/blog/secure-image-pipelines-ssrf-image-bombs) covers SSRF, redirects, and image bombs in depth. ## Verification cases [#verification-cases] * Allowed public source and allowed redirect. * Allowed hostname resolving to a prohibited address. * Redirect from an allowed host to a private or unlisted host. * Expired and malformed viewer signatures. * Key rotation with old and new key IDs. * Presigned source expiry before a cold transform. * Cache hit after viewer revocation. * Authorization response with and without shared-cache headers. * Logs, analytics, errors, and support tooling checked for secret leakage. Run these in an isolated environment with synthetic fixtures. Do not probe cloud metadata endpoints or third-party private networks. ## Using Keenpix with private origins [#using-keenpix-with-private-origins] Keenpix currently fetches allowlisted HTTP(S) sources and does not accept arbitrary per-project origin authorization headers. Its signed delivery URLs protect who may construct transform requests; they are not a credential for a private S3, R2, or application origin. A public asset origin or an authenticated gateway you operate can fit. Review [source security](/docs/concepts/security) and [signed URLs](/docs/concepts/signed-urls). Keenpix is not the right fit for a path that requires vendor-managed private networking, storage workload identity, cookies, or arbitrary secret headers unless you add and operate a safe gateway. Highly sensitive or immediate-revocation assets may be better served through an authenticated application endpoint with deliberately limited caching. ## Sources and limitations [#sources-and-limitations] * [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html), reviewed September 2, 2026. * [Amazon S3 presigned URL documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html), reviewed September 2, 2026. * [Cloudflare R2 presigned URLs](https://developers.cloudflare.com/r2/api/s3/presigned-urls/), reviewed September 2, 2026. * [HTTP caching, MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching), reviewed September 2, 2026. This guide does not establish compliance with a law, certification, or customer contract. Threat-model the actual asset, users, jurisdictions, infrastructure, and incident obligations with qualified security and legal reviewers. ## Related guides [#related-guides] * [Bring-your-own-origin architecture](/blog/bring-your-own-origin-image-cdn-architecture) * [Signed image URLs with HMAC](/blog/signed-image-urls-hmac) * [Security and data handling](/security) --- Markdown path: https://keenpix.com/blog/reproducible-image-performance-measurement.md # How to measure image CDN performance reproducibly > Build a repeatable image delivery test that separates cold transforms, warm caches, transfer, decoding, rendering, and field experience. Canonical HTML: [https://keenpix.com/blog/reproducible-image-performance-measurement](https://keenpix.com/blog/reproducible-image-performance-measurement) Author: Raed Bahri Published: 2026-09-02 Last reviewed: 2026-09-02 An image CDN benchmark is reproducible only when it fixes the source bytes, requested output, cache state, client, network, region, run order, and acceptance thresholds. Report cold transform and warm delivery separately. A single Lighthouse score, one `curl` duration, or a provider's published compression percentage cannot establish how your application will perform. ## Decide the question before the tool [#decide-the-question-before-the-tool] Different questions need different evidence: | Question | Evidence | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | | Does the transform output meet the visual requirement? | fixed source set, decoded dimensions, visual review, objective metric only if its limits are understood | | Is warm delivery fast in a target region? | repeated exact URL, verified cache signal, connection and transfer timing | | Is a cold transform within the product budget? | new deterministic variant, transform-region timing, memory and error telemetry | | Does the page improve for users? | controlled page test plus field Core Web Vitals segmented by route/device/region | | Will origin load fall? | origin request and byte counts before and after, matched to delivered traffic | Do not convert one answer into another. A smaller file can still be discovered late, decoded slowly, or served from a distant cache. A faster laboratory run does not prove a field improvement. ## Build the representative corpus [#build-the-representative-corpus] Include enough variation to expose different encoder and decoder behavior: * photographic hero images with texture and gradients; * product images with sharp edges and transparent backgrounds; * screenshots with small text; * portraits or subject-sensitive crops; * large-dimension inputs near your accepted limits; * animated inputs if the product supports them; * already-compressed inputs and incorrect MIME metadata. Store a checksum for every source. Record the license or ownership basis for the fixtures. Never benchmark with private customer assets unless the test environment and publication terms explicitly permit it. ## Freeze the request contract [#freeze-the-request-contract] For every output, record: ```json { "sourceSha256": "...", "sourceBytes": 0, "sourceWidth": 0, "sourceHeight": 0, "outputWidth": 0, "outputHeight": 0, "fit": "cover", "position": "centre", "quality": 80, "format": "webp", "accept": "image/webp,image/*", "region": "named test location", "client": "browser and version", "networkProfile": "declared or unthrottled" } ``` Use explicit formats for encoder comparisons. Test `fmt=auto` separately because the `Accept` header and cache variation become part of the experiment. ## Separate the three delivery states [#separate-the-three-delivery-states] ### Cold transform [#cold-transform] Request a new, bounded variant that has never been cached. Confirm it is new through a versioned fixture or a controlled cache namespace, not an unbounded random query that pollutes production. Record DNS, connect, TLS, time to first byte, download, status, response headers, output bytes, dimensions, and server-side transform timing if available. ### Warm service cache [#warm-service-cache] Repeat the exact URL with the exact headers. Require an explicit cache signal such as `Age`, a documented cache-status header, or server telemetry. Lower duration alone is not proof because connections, DNS, and operating-system caches also warm. ### Browser page load [#browser-page-load] Load a representative page and record which candidate the browser chose (`currentSrc`), when it was discovered, fetch priority, transferred bytes, decoded dimensions, render size, and whether it was the Largest Contentful Paint candidate. Run both desktop and mobile layouts. ## A repeatable run protocol [#a-repeatable-run-protocol] 1. Pin the application revision, configuration revision, browser version, source corpus, and test location. 2. Warm DNS and TLS only if the protocol says so; otherwise use a new browser context for every sample. 3. Alternate provider or configuration order to reduce time-of-day bias. 4. Run enough samples to show the distribution. Publish every valid run and the exclusion rule, not only the fastest result. 5. Report median and tail values separately. Do not combine cold and warm samples. 6. Save response headers and output checksums so an unexpectedly different format cannot masquerade as a speed improvement. 7. Inspect failed runs. Exclude only for a predeclared reason and retain the raw record. Example acceptance table: | Measure | Acceptance threshold | Why | | ----------------- | ----------------------------------------- | --------------------------------------------- | | Output dimensions | exact requested size | prevents a smaller wrong image from “winning” | | Visual review | no rejected crop or text artifact | protects the user-visible result | | Warm cache | explicit hit on repeated request | separates transform from delivery | | HTTP failures | zero in the declared corpus/run | exposes unsupported inputs and capacity | | Page candidate | no materially oversized `currentSrc` | verifies responsive markup | | Layout | no image-caused shift in tested viewports | protects rendering stability | Thresholds must come from your product requirement. The table is a template, not a universal benchmark. ## Core Web Vitals without overclaiming [#core-web-vitals-without-overclaiming] Largest Contentful Paint is a page experience metric, not an image-CDN metric. Image transfer can contribute, but server response, discovery delay, render delay, fonts, JavaScript, and layout decisions also matter. Use lab tools to diagnose; use field data to understand eligible real-user experience. Segment field results before and after the change and preserve traffic, device, and geography differences as limitations. Google's current “good” LCP guidance is 2.5 seconds or less at the 75th percentile, but meeting that threshold does not prove the CDN caused the result. Review the source and date whenever publishing a volatile threshold. ## Common benchmark failures [#common-benchmark-failures] * Comparing different source images or dimensions. * Treating `Content-Length` reduction as equivalent visual quality. * Timing a cold service against a warm service. * Omitting failed or throttled runs. * Using a random query parameter that bypasses normal cache identity. * Comparing a full media platform with a transform-only service without stating scope. * Reporting one geography as global performance. * Calling synthetic Lighthouse results “real-user Core Web Vitals.” * Publishing a percentage improvement without raw inputs and procedure. ## Using Keenpix in the protocol [#using-keenpix-in-the-protocol] Use a project with an explicit origin allowlist, a versioned test corpus, and a finite set of widths. Capture Keenpix response headers, output bytes, decoded dimensions, cold and warm timings, and the delivery analytics available to your deployment. The [endpoint reference](/docs/reference/endpoint) defines parameters; the [analytics concept](/docs/concepts/analytics) explains product telemetry. Keenpix is not the right comparison candidate if the required workload is video, DAM operations, upload processing, private origin headers, or transforms it does not support. Do not substitute an easier image workload and generalize the result. ## Sources and limitations [#sources-and-limitations] * [Largest Contentful Paint, web.dev](https://web.dev/articles/lcp), reviewed September 2, 2026. * [How to measure speed with the Performance panel, Chrome DevTools](https://developer.chrome.com/docs/devtools/performance/), reviewed September 2, 2026. * [Resource Timing Level 2, W3C](https://www.w3.org/TR/resource-timing-2/), reviewed September 2, 2026. * [Server Timing, W3C](https://www.w3.org/TR/server-timing/), reviewed September 2, 2026. Network conditions and provider infrastructure change. A published run is a dated observation under declared conditions, not a permanent ranking, SLA, or promise of savings. ## Related guides [#related-guides] * [AVIF vs WebP in production](/blog/avif-vs-webp-production-caching) * [Responsive image CDN guide](/blog/responsive-image-cdn-guide) * [Image CDN cost calculator](/image-cdn-cost-calculator) --- Markdown path: https://keenpix.com/blog/responsive-image-cdn-guide.md # Responsive image CDN guide: srcset, sizes, DPR, and formats > Build responsive image CDN URLs with srcset and sizes, choose width candidates, handle DPR and format negotiation, and avoid common cache mistakes. Canonical HTML: [https://keenpix.com/blog/responsive-image-cdn-guide](https://keenpix.com/blog/responsive-image-cdn-guide) Author: Raed Bahri Published: 2026-07-11 Last reviewed: 2026-07-11 A responsive image CDN works best when **HTML tells the browser what the layout needs and the image service supplies the candidate files**. The CDN should not guess the rendered width from the user agent. Use `srcset` for candidates, `sizes` for layout intent, intrinsic dimensions for aspect ratio, and content-appropriate alt text. The browser then selects a candidate using viewport width, display density, current layout information, supported formats, and network conditions. You provide good options; you do not dictate one universal download. ## Start with width candidates [#start-with-width-candidates] For a content image whose rendered width changes with the layout, width descriptors are the most flexible pattern: ```html Diagram of an image request moving through transform and CDN caches ``` The `400w` descriptor means the resource is 400 CSS pixels wide. The `sizes` value says how wide the image will render at each layout condition. The browser uses both to select a candidate. Do not copy the example widths blindly. Inspect the component's actual rendered widths, include high-density displays, and choose a small reusable set. Too few candidates can waste bytes; too many can fragment caches and create needless work. ## How to choose a width set [#how-to-choose-a-width-set] A practical process is: 1. Record the component's smallest and largest rendered widths at supported breakpoints. 2. Multiply important widths by the highest device-pixel ratio you intend to serve. 3. Choose a geometric sequence or a compact design-system list rather than every possible pixel width. 4. Cap the result at the source image's useful resolution. 5. Test byte differences. Remove adjacent candidates whose file sizes are too similar to matter. For example, a card that renders around 320, 480, and 720 CSS pixels might offer 320, 480, 640, 960, 1280, and 1440-pixel files. Whether that is excessive depends on the sources, encoding, traffic, and cache behavior. A benchmark must include those details before presenting one set as optimal. ## Width descriptors vs density descriptors [#width-descriptors-vs-density-descriptors] Use density descriptors such as `1x` and `2x` when the image has a stable CSS size: ```html Raed Bahri ``` Do not mix width (`400w`) and density (`2x`) descriptors in one `srcset`. For a fluid layout, width descriptors plus `sizes` usually express the requirement more accurately. An image CDN may also expose a `dpr` transform parameter. Be careful not to apply density twice. If the URL already requests a 128-pixel physical output for a 64-pixel slot, adding `dpr=2` again can create a 256-pixel output. ## Format negotiation with picture or Accept [#format-negotiation-with-picture-or-accept] There are two common strategies. The first is server-side negotiation. A URL such as `fmt=auto` lets the image service inspect the request's `Accept` header and return a supported format. If a CDN sits in front, its cache key must distinguish responses correctly—typically through `Vary: Accept` or a normalized format key. Test this behavior; a misconfigured shared cache can serve the wrong format. The second is explicit `` sources: ```html Abstract Keenpix transformation pipeline ``` Explicit sources create predictable URLs and cache entries but more markup. Automatic negotiation keeps markup smaller but makes the cache configuration more important. Neither approach guarantees that AVIF is smaller for every asset; compare real outputs. ## Crop, fit, and art direction [#crop-fit-and-art-direction] Resizing preserves or adjusts dimensions. Art direction changes the composition—for example, using a tighter crop on mobile. Use `` with media conditions when the content needs a different crop, not only a smaller copy. ```html The Keenpix maintainer reviewing image delivery logs ``` Automatic face or subject cropping can be useful, but it is a product-specific feature and can fail. Editorially important crops deserve human review. ## Loading and priority [#loading-and-priority] Responsive sizing does not decide when an image loads. The likely largest-contentful image should not be lazily loaded merely to satisfy a blanket rule. Below-fold images are usually good candidates for `loading="lazy"`. Browsers also support `fetchpriority="high"` for a genuinely critical image, but overusing high priority removes its value. Always reserve space with `width` and `height` or a stable CSS aspect ratio. Format conversion cannot prevent layout shift caused by missing dimensions. ## Cache and URL design [#cache-and-url-design] A responsive image CDN can generate many variants. Keep its grammar bounded: * allow only approved source hosts; * normalize parameter ordering and defaults; * restrict output dimensions and quality ranges; * sign URLs when arbitrary transforms would create abuse risk; * use versioned source URLs for long-lived immutable caching; * avoid per-request tokens in the cache key unless they are normalized away safely; * monitor cache misses and unusual width combinations. A crawler or buggy component can request thousands of unique widths even if transformations are not separately billed. The cost may show up as CPU, cache storage, origin traffic, or cold latency. ## Framework components still need inspection [#framework-components-still-need-inspection] Next.js, Nuxt, Astro, and other frameworks can generate `srcset`, `sizes`, and loader URLs. They reduce repetitive code, but defaults may not match the actual layout. Inspect rendered HTML and browser network requests. Verify that a custom loader preserves widths, quality, and absolute-source encoding correctly. Keenpix framework examples live in the [documentation](/docs/frameworks). The category article [what is an image CDN?](/blog/what-is-an-image-cdn) explains the request lifecycle, while [image CDN vs traditional CDN](/blog/image-cdn-vs-traditional-cdn) explains where transformation ends and edge caching begins. ## Verification checklist [#verification-checklist] Before shipping: * resize the viewport and confirm the selected resource width in browser developer tools; * test standard and high-density displays; * confirm correct AVIF/WebP/JPEG fallbacks; * verify `Content-Type`, `Cache-Control`, `Vary`, dimensions, and response status; * ensure the largest candidate is not larger than the source can support; * confirm meaningful alt text and empty alt values for decoration; * test cache reuse after a warm request; * test a changed source with the planned invalidation or versioning method; * measure transferred bytes and user-visible loading with representative pages. ## Sources and verification [#sources-and-verification] This guide was checked on July 11, 2026 against [MDN responsive images](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Responsive_images), [MDN's `` reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/picture), [web.dev responsive images](https://web.dev/learn/images/responsive-images), and [MDN's `Accept` header reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept). Code examples use placeholder domains and are not performance benchmarks. --- Markdown path: https://keenpix.com/blog/safe-image-cdn-rollouts-and-rollbacks.md # Safe image CDN rollouts and rollbacks > Canary image delivery changes with explicit acceptance gates, cache-aware rollback, observability, and a tested path back to the previous URL generator. Canonical HTML: [https://keenpix.com/blog/safe-image-cdn-rollouts-and-rollbacks](https://keenpix.com/blog/safe-image-cdn-rollouts-and-rollbacks) Author: Raed Bahri Published: 2026-09-02 Last reviewed: 2026-09-02 A safe image CDN rollout changes one reversible boundary at a time and keeps the previous URL generator, configuration, and delivery path usable until the new path has survived representative traffic and cache lifetimes. “The deployment completed” is not an acceptance test. “The endpoint is healthy now” is not proof that replacement caused no interruption. ## Define ownership and stop conditions [#define-ownership-and-stop-conditions] Name one owner for the application URL change, one for the delivery service, and one for the source origin. Agree on a decision channel and stop conditions before the canary. Example stop conditions: * an increase in image 4xx or 5xx above the declared baseline; * any cross-tenant, private-origin, or signature validation defect; * incorrect crops or material visual artifacts in the acceptance corpus; * missing cache variation that can serve an unsupported format; * origin request or byte growth above the planned envelope; * tail delivery or page-render regression beyond the declared threshold; * inability to identify the exact running revision or configuration. Use numbers from your own baseline. Do not copy generic percentages into production policy. ## Make the change reversible [#make-the-change-reversible] Prefer a URL-generation switch in the application or edge configuration: ```ts const imageBaseUrl = config.imageDelivery === 'new' ? 'https://new-images.example.com' : 'https://old-images.example.com' ``` The switch must be deployable without the new service. If the new hostname is down, a control panel hosted only behind that hostname is not a rollback mechanism. Keep the old DNS, certificates, configuration, and credentials alive through the observation window. Avoid destructive migrations of source assets during the delivery cutover. Copy or read from the existing origin first. Moving masters and changing delivery in one event creates two possible causes for every failure. ## Stage 0: establish a baseline [#stage-0-establish-a-baseline] Record the exact application revision, configuration, source corpus, delivery routes, browser versions, regions, and time window. Capture: * image response status by class; * browser-selected candidates and decoded dimensions; * cold and warm timing distributions; * cache-hit signals and origin traffic; * transform errors and saturation; * page LCP and image-caused layout shifts in lab, plus field data when available; * current billing units and projected cost model. Unknown evidence remains unknown. Do not record a missing metric as zero. ## Stage 1: shadow and fixture validation [#stage-1-shadow-and-fixture-validation] Generate the new URLs without serving them to users. Test a versioned corpus across formats, widths, fits, crops, transparency, animation, large safe inputs, invalid parameters, blocked origins, redirects, and signed URLs. Compare output dimensions, visual result, MIME type, bytes, cache headers, and error semantics. ## Stage 2: canary a bounded surface [#stage-2-canary-a-bounded-surface] Choose a route, tenant, asset class, or deterministic traffic slice that can be identified in logs. Start small enough that rollback is quick but large enough to exercise normal cache and origin behavior. Keep monitoring split by old and new path. Do not use only the homepage if the hard cases live in product galleries, user uploads, emails, or framework-specific routes. ## Stage 3: expand by evidence [#stage-3-expand-by-evidence] Expand one dimension at a time—more traffic, another region, another asset class, or another application. Hold long enough to observe cold cache fills, routine traffic peaks, background prewarming, and origin rate limits. A five-minute healthy window does not cover a 24-hour cache or daily traffic cycle. ## Rollback is a new traffic change [#rollback-is-a-new-traffic-change] Switching URLs back stops new requests from choosing the new path, but caches and already-rendered pages can continue using it. Plan for: * browser cache and service-worker lifetime; * HTML or API responses containing the new URLs; * edge cache freshness and stale serving; * DNS TTL if the hostname itself changes; * queued prewarm jobs; * transformed variants written to shared storage; * signed URLs that remain valid. If the old path was removed or its source access revoked, rollback may fail after the switch. Exercise it before the canary and again during a controlled test. ## Deployment health versus service continuity [#deployment-health-versus-service-continuity] Container replacement, rolling updates, blue-green deployment, and DNS cutover have different overlap guarantees. To claim uninterrupted availability, collect continuous external probes across the full replacement window and prove that old and new capacity overlapped. Eventual 200 responses prove recovery, not zero downtime. For self-hosted stacks, health checks should separate liveness, readiness, and dependency health. A process can be alive while unable to fetch origins, write caches, or serve transforms. ## Verification after rollback [#verification-after-rollback] * New page loads emit only the old delivery URL where expected. * Old and new cached URLs have known behavior during their remaining lifetime. * Origin traffic, errors, and latency return to the expected envelope. * No queue or prewarm job continues expanding the failed configuration. * The incident record names the exact revisions, times, symptoms, decision, and remaining cleanup. * The failed path stays available for evidence only if doing so is safe; otherwise preserve sanitized logs and fixtures. ## Using Keenpix for a controlled rollout [#using-keenpix-for-a-controlled-rollout] For managed Keenpix, create the project and allowlist, validate representative transforms, then put the Keenpix URL generator behind an application configuration switch. For self-hosting, follow the [health and operations](/docs/self-hosting/health-and-operations) and [CDN configuration](/docs/self-hosting/cdn) guides, including liveness/readiness and cache-key validation. Use [symptom-first troubleshooting](/blog/image-delivery-troubleshooting-by-symptom) when a gate fails. Keenpix is not the right fit if you cannot keep an independent rollback path, require a transform or authenticated-origin capability it lacks, or do not have an owner for the CDN, origin, cache, and incident response work. A platform already integrated with your framework or media workflow may be safer. ## Sources and limitations [#sources-and-limitations] * [Docker Compose production guidance](https://docs.docker.com/compose/how-tos/production/), reviewed September 2, 2026. * [Kubernetes Deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/), reviewed September 2, 2026. * [HTTP caching, MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching), reviewed September 2, 2026. * [Site Reliability Engineering: canarying releases, Google](https://sre.google/sre-book/reliable-product-launches/), reviewed September 2, 2026. The procedure does not guarantee zero downtime. Deployment platforms, CDNs, DNS providers, and application caches have different replacement and consistency behavior; verify the actual topology. ## Related guides [#related-guides] * [Self-host an image CDN with Docker](/blog/self-host-image-cdn-docker) * [Transform cache stampedes and capacity](/blog/image-transform-cache-stampedes-capacity) * [Bring-your-own-origin architecture](/blog/bring-your-own-origin-image-cdn-architecture) --- Markdown path: https://keenpix.com/blog/secure-image-pipelines-ssrf-image-bombs.md # Secure image pipelines: SSRF to image bombs > Defend image fetch and transform services with origin allowlists, redirect revalidation, byte and pixel ceilings, timeouts, and observable failures. Canonical HTML: [https://keenpix.com/blog/secure-image-pipelines-ssrf-image-bombs](https://keenpix.com/blog/secure-image-pipelines-ssrf-image-bombs) Author: Raed Bahri Published: 2026-08-23 Last reviewed: 2026-09-03 Treat every remote image as an untrusted network request followed by an untrusted decoder input. A safe pipeline validates the source host, resolves and rejects internal addresses, pins the checked address to the connection, disables automatic redirects, revalidates every redirect target, caps bytes while streaming, limits decoded pixels and frames, bounds output dimensions, and runs inside a resource-limited process. No single check covers the chain. A hostname allowlist without IP validation can reach internal services through DNS. A `Content-Length` check without a streaming ceiling trusts a header the origin controls. A 5 MB compressed file can still expand into a huge bitmap. A pixel ceiling does not replace a request timeout or a memory limit. ## The defense sequence [#the-defense-sequence] ```text untrusted URL │ ├─ parse with a URL library; allow only http/https ├─ require an exact host or deliberate subdomain allowlist match ├─ resolve DNS; reject private, loopback, link-local, multicast, metadata ranges ├─ connect to the address that was checked │ ├─ receive redirect? ── yes ──> resolve relative Location and repeat every check │ stop after a small fixed hop limit │ ├─ enforce timeout and streamed byte ceiling ├─ decode with pixel/page/channel safety limits ├─ apply bounded dimensions and expensive-operation policy └─ encode, cache, observe, and release resources ``` Add network egress policy around the process. Application checks reduce risk, but a container or VM that cannot route to metadata and private control-plane ranges gives you another independent boundary. ## Threats and the control that actually stops them [#threats-and-the-control-that-actually-stops-them] | Threat | What the attacker changes | Required control | Observable rejection | | ------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------- | | Basic SSRF | URL points at localhost, metadata, or a private service | Protocol/host validation plus public-IP enforcement | `400` or `403`; blocked target class in security log | | DNS rebinding | Allowed name resolves differently between validation and connect | Pin the validated address or use a trusted egress proxy | Resolution/connect mismatch or blocked IP | | Redirect pivot | Public URL redirects to an internal target | Disable automatic following; validate each `Location` | Redirect-policy failure with hop number | | Oversized transfer | Missing or false `Content-Length`, endless body | Streaming byte counter and timeout | `413` or `504`; bytes read before abort | | Decompression bomb | Small file declares enormous dimensions | Decode-time pixel/channel/page ceilings | Decoder-limit rejection plus memory, OOM, and restart monitoring | | Animated-image bomb | Many frames multiply work and memory | Disable animation by default; cap pages/work when enabled | Rejected animation or bounded frame count | | Variant abuse | Valid source with changing expensive transforms | Parameter bounds, finite ladders, signed URLs, rate limits | `403` signature failure, throttling, miss-cardinality alert | | Decoder exploit | Malformed bytes target a library bug | Patched decoder, sandbox, least privilege, no secrets | Crash/restart alert and quarantined source | ## Observation: Keenpix's current request path [#observation-keenpixs-current-request-path] Verified against repository code and tests on August 23, 2026, Keenpix currently applies these controls before a cold transform: 1. It parses the source with the platform `URL` implementation and accepts only `http:` and `https:`. 2. It fails closed when a project has no allowed origins. A host must equal an allowed name or be its subdomain. 3. It resolves a preferred IPv4 address, falling back to the resolver result, and rejects the private/routed classes implemented in `safe-origin.ts`, including loopback, RFC 1918, link-local, CGNAT, multicast/reserved IPv4, IPv6 unique-local/link-local/multicast, and mapped IPv4 forms. 4. It pins that selected address into the HTTP or HTTPS agent lookup so the connect does not perform a second unconstrained DNS decision. 5. It disables automatic redirects. Every accepted redirect is resolved relative to the current URL and passed through the complete origin validation again. 6. It checks declared length when present and always counts streamed bytes, aborting after the configured ceiling. 7. It passes raster bytes to Sharp with `limitInputPixels` and `failOn: 'truncated'`, then caps output dimensions inside the transform steps. The allowlist is checked before the internal cache read. A previously cached object does not let a source hostname bypass a later allowlist decision. Optional [signed URLs](/docs/concepts/signed-urls) are also verified before the cache lookup. These are code observations, not a claim that the system is immune to SSRF, denial of service, or decoder vulnerabilities. The selected-address lookup is not the same as enumerating every A and AAAA answer, and application checks are not a substitute for egress controls. ## Current repository defaults and hard stops [#current-repository-defaults-and-hard-stops] Self-hosted operators can override environment defaults, so record the effective values for each deployment instead of assuming these are active everywhere. | Boundary | Repository default/current rule | Failure behavior | | --------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------- | | Origin body | 50 MiB streamed maximum | `413 Origin image too large` | | Origin request | 10 seconds per attempt | `504 Origin timed out` | | Redirects | Up to three redirects followed to a final fourth request | Another redirect ends as `502 Too many redirects` | | Raster input | 50,000,000 decoded pixels | Sharp rejects the input; Keenpix returns a safe `502` message | | Output dimension | 4096 pixels runtime maximum | Resize is capped by runtime/project policy | | SVG input | 2 MiB hard limit in the SVG optimizer | Rejected before optimization | | Watermark body | 5 MiB origin maximum | `413` when exceeded | | Sharp worker threads | One libvips thread per image in current setup | Reduces per-transform thread multiplication; codec libraries may add threads | | Truncated raster data | `failOn: 'truncated'` | Decode fails instead of accepting partial pixels | The origin byte limit bounds the buffer Keenpix reads before decoding; it does not predict decoded memory. The pixel limit bounds declared width × height; it does not turn a 50-million-pixel image into a cheap transform. Animation, color profiles, codec behavior, intermediate operations, and output buffers still consume CPU and memory. ## Animation limits do not provide moderation [#animation-limits-do-not-provide-moderation] Keenpix defaults the `animated` transform option to false. When a caller explicitly enables it, the current Sharp input path enables multi-page processing, but the repository does not currently expose a separate documented maximum frame count or total animated-pixel budget. The general input-pixel, byte, timeout, output-dimension, process-memory, and capacity boundaries still apply, yet none is equivalent to a deliberate animation policy. If animated delivery is part of your product, define an ingestion-time ceiling for frame count, total decoded pixels across frames, duration, loops, and processing time. Exercise the limit with adversarial fixtures, not only a small GIF. If animation is unnecessary, reject or flatten it before publication and leave downstream animation disabled. Moderation is a separate decision again. Decoder success means the bytes are processable; it does not mean the content is acceptable. Keenpix does not currently provide upload quarantine, malware scanning, content classification, human review, or an appeals workflow. Put those gates in the system that owns uploads, and expose only approved, immutable source versions to the delivery layer. The [user-upload pipeline guide](/blog/user-upload-image-pipeline-design) shows how to keep validation, moderation, publication, and delivery as distinct states. ## Safe fetch pseudocode [#safe-fetch-pseudocode] The important property is ordering. Do not resolve safely and then call a client that performs its own unrelated lookup or follows redirects automatically. ```ts async function fetchUntrustedImage(rawUrl: string) { let current = await resolveAllowedPublicTarget(rawUrl) for (let attempt = 0; attempt < 4; attempt += 1) { const response = await requestPinnedAddress(current, { followRedirects: false, maxBodyBytes: 20 * 1024 * 1024, timeoutMs: 8_000, }) if (response.isRedirect) { if (attempt === 3) throw new Error('redirect limit exceeded') current = await resolveAllowedPublicTarget( new URL(response.location, current.url).toString(), ) continue } if (!response.ok) throw new Error(`origin returned ${response.status}`) return response.readWithHardByteCeiling() } throw new Error('unreachable') } ``` `resolveAllowedPublicTarget` must use a URL parser, not a regular expression; compare normalized hostnames against an allowlist; reject credentials and unexpected ports if your product does not need them; inspect IPv4 and IPv6; and return the checked address for the connection. The pseudocode leaves library-specific TLS/SNI details to the implementation. Copying it without a pinned, certificate-valid HTTPS connection is not sufficient. OWASP recommends disabling redirect following to prevent redirect-based validation bypasses. If product requirements need redirects, manual revalidation like the sequence above is the safer contract. ## Byte limits and image bombs are different gates [#byte-limits-and-image-bombs-are-different-gates] A body ceiling answers “how much compressed data will I buffer?” A pixel ceiling answers “how large a raster will I permit the decoder to construct?” Enforce both: ```ts import sharp from 'sharp' const output = await sharp(untrustedBytes, { animated: false, failOn: 'warning', limitInputChannels: 4, limitInputPixels: 40_000_000, sequentialRead: true, }) .resize({ width: 2048, height: 2048, fit: 'inside', withoutEnlargement: true }) .webp({ quality: 80 }) .toBuffer() ``` Sharp's official constructor documentation recommends its default `failOn: 'warning'` level for untrusted input and documents pixel, channel, page, and “unlimited” safety options. Keenpix currently uses `failOn: 'truncated'`; that is a repository observation, not the stricter general recommendation shown above. Leave animation off unless the product needs it. If animation is required, test inputs with many frames and enforce an explicit page/work budget around the decoder. A width × height check alone does not express total animated work. Do not set Sharp's `unlimited: true` for an internet-facing transform service. Keep the decoder and its native dependencies patched, run as a non-root user, mount a read-only filesystem where possible, remove cloud credentials from the transform process, and place temporary storage on a bounded volume. ## Redirect policy needs a business decision [#redirect-policy-needs-a-business-decision] Redirects are not automatically safe or unsafe. Decide what your pipeline needs: | Policy | Benefit | Cost | | -------------------------------------------- | ----------------------------------------- | --------------------------------------------------------- | | Reject every redirect | Smallest attack surface and simplest logs | Breaks signed object redirects and some asset migrations | | Allow same-host redirects only | Supports path/version changes | Does not support deliberate CDN host changes | | Allow redirects within the project allowlist | Flexible and auditable | Every hop still needs DNS/IP validation and a fixed limit | | Follow anything the HTTP client accepts | Convenient | Reopens SSRF and unbounded-loop risk | Keenpix currently uses the third policy. A redirect to a different host succeeds only if that host is also allowed and resolves to an accepted public address. Keep redirect targets out of logs when URLs can contain tokens; log the normalized host, path class, hop count, and rejection reason instead. ## Network controls behind the application [#network-controls-behind-the-application] Recommended production boundaries: * deny routes from the transform workload to cloud metadata, loopback, private subnets, cluster control planes, and database networks; * allow DNS only through the resolver you monitor; * route public origin traffic through an egress proxy when you need one enforcement and audit point; * separate the image data plane from control-plane credentials; * run decoder work in a container or sandbox with CPU, memory, process, file, and temporary-storage limits; * keep the transform health endpoints private, as described in [health and operations](/docs/self-hosting/health-and-operations). Network policy should fail independently from URL validation. Test both. A unit test that mocks DNS does not prove a production container cannot reach `169.254.169.254`. ## Production test cases [#production-test-cases] Use hosts and fixtures you control. Never probe third-party internal ranges. | Test fixture | Expected result | | ------------------------------------------------ | ------------------------------------------ | | Allowed public JPEG under every limit | Successful transform and later cache hit | | Disallowed public hostname | `403` before origin fetch | | Allowed name resolving to a private test address | `403` before connect | | Public host redirecting to a disallowed host | `403` on redirect validation | | Four-redirect chain | Fixed redirect-limit failure | | False small `Content-Length`, body above ceiling | Stream aborted at the real byte limit | | Slow body exceeding attempt timeout | `504` without an accumulating socket | | Tiny compressed image declaring too many pixels | Decode rejection without container restart | | Truncated image | Deterministic decoder rejection | | Animated fixture above the chosen work policy | Rejected or animation disabled | | Tampered signed width/source | `403` before cache and transform work | Capture response status, normalized rejection category, total duration, bytes read, redirect hops, decoder failure class, process RSS, restart count, and whether the request reached the origin. Do not log source query secrets or signing keys. ## Failure signals worth alerting on [#failure-signals-worth-alerting-on] * a rise in `403` private-address or disallowed-host failures; * redirect-limit failures or a new redirect target host; * `413` responses and origin bodies ending exactly at the configured ceiling; * `504` responses, open-socket growth, or origins approaching the timeout; * decoder failures clustered by source host or file format; * process OOM kills, native crashes, restart loops, or sustained RSS growth after requests complete; * high cache-miss cardinality with normal source traffic; * egress-deny logs for metadata, database, or cluster ranges; * a patch release from Sharp/libvips or an image codec that addresses a security issue. An HTTP `502` for an invalid image is expected input rejection, not proof that the service crashed. A healthy liveness probe after every request is also insufficient: correlate rejection status with memory and restart telemetry. ## Production checklist [#production-checklist] * [ ] Allow only `http` and `https`; reject credentials and unused ports. * [ ] Fail closed when no source hosts are configured. * [ ] Normalize and allowlist hosts without substring matching mistakes. * [ ] Validate public IPv4 and IPv6 results and connect to the checked address. * [ ] Disable automatic redirects; revalidate each hop and cap the chain. * [ ] Enforce both declared-length and streamed-byte ceilings. * [ ] Set per-attempt and total-request deadlines. * [ ] Limit decoded pixels, channels, pages/frames, output dimensions, and expensive operations. * [ ] Keep animation disabled unless it has a measured product requirement. * [ ] Run patched native decoders with least privilege and bounded resources. * [ ] Add network egress denies for metadata and internal networks. * [ ] Require [signed image URLs](/blog/signed-image-urls-hmac) when callers must not create arbitrary variants. * [ ] Test the rejection paths in the deployed network, not only in unit tests. ## Explicit limits and non-guarantees [#explicit-limits-and-non-guarantees] An origin allowlist does not authenticate the bytes at that host. A signed transform URL prevents query tampering; it does not make an origin private, stop replay, or patch a decoder. DNS pinning closes one time-of-check/time-of-use window but does not replace inspection of all relevant addresses and egress policy. The defaults listed here come from the current public Keenpix source. They may be overridden in a self-hosted deployment, and this article does not assert the effective managed-cloud values. Review [Keenpix security and data handling](/security), the [security concept reference](/docs/concepts/security), [parameters](/docs/reference/parameters), and your own deployment configuration. ## Continue the operations cluster [#continue-the-operations-cluster] Pair these input controls with [a bounded cache-key design](/blog/image-cdn-cache-keys-vary-accept) and [cache-stampede and transform-capacity planning](/blog/image-transform-cache-stampedes-capacity). To run the stack yourself, use the [self-hosting operations guide](/docs/self-hosting/health-and-operations) and [Docker production checklist](/blog/self-host-image-cdn-docker). To use the managed path, start with the [cloud quickstart](/docs/getting-started/cloud-quickstart) or [create a trial](/signup). ## Sources and verification [#sources-and-verification] Verified August 23, 2026 against the official [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html), [Sharp constructor and safety options](https://sharp.pixelplumbing.com/api-constructor/), and current Keenpix [origin validation](https://github.com/lord007tn/keenpix/blob/master/packages/transform/src/origin/safe-origin.ts), [pinned fetch and redirect handling](https://github.com/lord007tn/keenpix/blob/master/packages/transform/src/origin/fetch-image.ts), [Sharp pipeline](https://github.com/lord007tn/keenpix/blob/master/packages/transform/src/sharp/pipeline.ts), [runtime defaults](https://github.com/lord007tn/keenpix/blob/master/apps/transform/src/env.ts), and transform tests. The defense-in-depth recommendations go beyond current application code and are labeled accordingly; no security guarantee is implied. --- Markdown path: https://keenpix.com/blog/self-host-image-cdn-docker.md # Self-host an image CDN with Docker: production checklist > Deploy Keenpix with a pinned Docker image, persistent Postgres, health checks, CDN cache rules, backups, and a repeatable smoke test. Canonical HTML: [https://keenpix.com/blog/self-host-image-cdn-docker](https://keenpix.com/blog/self-host-image-cdn-docker) Author: Raed Bahri Published: 2026-08-15 Last reviewed: 2026-08-15 `docker compose up -d` can leave you with seven healthy containers and an unsafe deployment. The database may have no backup, image tags may drift across services during the next pull, or a proxy may cache the sign-in page along with transformed images. This setup treats the first successful container start as the beginning of the job. It uses the Compose file in the Keenpix repository, pins a release, and checks the pieces that carry state. ## What the Compose stack runs [#what-the-compose-stack-runs] The repository's `docker-compose.yml` starts seven services: * PostgreSQL 18 on the private Compose network, with a named database volume; * Dragonfly for queues and shared hot-cache metadata; * MaxIO for the shared S3-compatible image cache; * the app control plane, transform data plane, BullMQ worker, and standalone docs service. The stateful and Keenpix services have readiness checks. The app waits for its dependencies, runs Prisma migrations and the initial seed through its entrypoint, then serves `/api/health` on port 3000. The Keenpix containers run as the unprivileged `node` user. The database volume is durable state. Back it up. The image cache can be rebuilt from the origin and should not be treated as a backup of your source images. ## Pin the release before starting [#pin-the-release-before-starting] The Compose file defaults to the moving `latest` image. For a controlled installation, check out a release and pin the matching container tag: ```bash git clone --branch v0.3.0 --depth 1 https://github.com/lord007tn/keenpix.git cd keenpix cp .env.example .env ``` Set at least these values in `.env`: ```dotenv KEENPIX_APP_IMAGE=ghcr.io/lord007tn/keenpix-app:v0.3.0 KEENPIX_TRANSFORM_IMAGE=ghcr.io/lord007tn/keenpix-transform:v0.3.0 KEENPIX_WORKER_IMAGE=ghcr.io/lord007tn/keenpix-worker:v0.3.0 KEENPIX_DOCS_IMAGE=ghcr.io/lord007tn/keenpix-docs:v0.3.0 POSTGRES_PASSWORD= BETTER_AUTH_SECRET= BETTER_AUTH_URL=https://admin.images.example.com KEENPIX_APP_URL=https://admin.images.example.com KEENPIX_SUPER_ADMIN_EMAIL=admin@example.com KEENPIX_SUPER_ADMIN_PASSWORD= ``` Generate the auth secret instead of typing the placeholder: ```bash openssl rand -hex 32 ``` Then pull and start the exact image: ```bash docker compose pull docker compose up -d docker compose ps ``` Do not publish PostgreSQL's port to the internet. The supplied Compose file keeps it private and only publishes the Keenpix application port. ## Put TLS and caching in the right places [#put-tls-and-caching-in-the-right-places] Terminate HTTPS at Caddy, Nginx, Coolify's proxy, or a CDN. Set `BETTER_AUTH_URL` and `KEENPIX_APP_URL` to the public dashboard URL so cookies and generated links use the same origin. Cache only `/img/*`. Keep `/app`, `/api/auth`, `/api/health`, and the rest of the dashboard outside the cache rule. A broad "cache everything" rule can store authenticated or session-dependent responses. The cache key must include the complete path and query string. These requests are different image variants: ```text /img/https://assets.example.com/hero.jpg?project=store&w=640&fmt=webp /img/https://assets.example.com/hero.jpg?project=store&w=1200&fmt=webp ``` If you use `fmt=auto`, the outer cache must also separate variants by `Accept`. Use explicit `fmt=avif`, `fmt=webp`, or `fmt=jpeg` when the CDN cannot prove that behavior. ## Create a project and run a smoke test [#create-a-project-and-run-a-smoke-test] Sign in with the bootstrap admin account, create a project, and add the source hostname to its allowed-host list. Then request one known image: ```bash curl -sS -D transform.headers -o transformed.webp \ 'https://images.example.com/img/https://assets.example.com/catalog/chair.jpg?project=PROJECT_ID&w=1200&fmt=webp&q=80' ``` Check these facts instead of stopping at HTTP 200: * `Content-Type` is `image/webp`; * `Cache-Control` keeps the transform cacheable; * the returned dimensions match the request and source limits; * a source hostname outside the allowlist is rejected; * a repeated request becomes a hit at the cache layer you intended to test. Also check the service health and recent logs: ```bash curl -fsS https://admin.images.example.com/api/health docker compose logs --since=10m app ``` The health response proves that this instance can answer its configured checks. It does not test every source, transformation, or CDN region. ## Back up Postgres before the first upgrade [#back-up-postgres-before-the-first-upgrade] Create and test a database-backup routine before users add projects or invitations. A simple manual dump from the bundled service is a useful first check: ```bash docker compose exec -T postgres sh -lc \ 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \ > keenpix-$(date +%F).dump ``` Store backups away from the Docker host. A named volume protects data from routine container replacement, but it does not protect against disk loss, operator mistakes, or a damaged database. Before an upgrade, read the release notes, take a fresh dump, change all four `KEENPIX_*_IMAGE` variables to the selected version, then run: ```bash docker compose pull app transform worker docs docker compose up -d app transform worker docs docker compose ps ``` The entrypoint applies database migrations. Keep the previous image reference and a verified restore procedure available until the smoke tests pass. ## Capacity choices you still own [#capacity-choices-you-still-own] The default Compose cache cap is 8 GB. Set `KEENPIX_CACHE_MAX_BYTES` from the actual disk budget, then watch eviction behavior. The HTTP transform runtime does not currently expose a global concurrency setting; add bounded admission control at the proxy or orchestrator so cold traffic sheds before the host runs out of memory. Use the [cache stampede and transform capacity guide](/blog/image-transform-cache-stampedes-capacity) to measure a safe limit. Horizontal self-hosted replicas do not share Keenpix's local disk cache. The bundled stack uses MaxIO for its shared object-storage tier; production operators can replace it with R2 or another S3-compatible service. Put a CDN in front of the transform service. PostgreSQL also needs a deliberate high-availability and backup plan if the service matters to production traffic. ## When managed Keenpix is the better choice [#when-managed-keenpix-is-the-better-choice] Self-hosting removes the managed-service bill, not the operations work. Choose it when infrastructure control, data placement, or the ability to modify the AGPL-3.0 code matters enough to own upgrades, monitoring, backups, abuse controls, and incident response. Use the managed service when the team wants image delivery without maintaining that stack. The transform URL concepts are similar, but the operational responsibility is not. ## Sources and verification [#sources-and-verification] This guide was updated on August 18, 2026 for [Keenpix v0.3.0](https://github.com/lord007tn/keenpix/releases/tag/v0.3.0), the repository's four Dockerfiles and `docker-compose.yml`, the [deployment presets](/docs/self-hosting/presets), [configuration reference](/docs/self-hosting/configuration), and [CDN setup](/docs/self-hosting/cdn). The commands are an installation checklist, not a claim that every production deployment takes the same amount of time. --- Markdown path: https://keenpix.com/blog/self-host-vs-managed-image-optimization.md # Self-hosted vs managed image optimization: how to choose > Compare who owns the database, cache, delivery bill, upgrades, monitoring, and failure recovery in self-hosted and managed image optimization. Canonical HTML: [https://keenpix.com/blog/self-host-vs-managed-image-optimization](https://keenpix.com/blog/self-host-vs-managed-image-optimization) Author: Raed Bahri Published: 2026-06-24 Last reviewed: 2026-08-15 The self-hosted and managed editions use the same transform grammar, but they are not the same operational decision. One puts Postgres, cache capacity, updates, and incidents on your team. The other puts the service boundary and delivery bill with Keenpix. Start by deciding which team will own failures in the image path. Cost makes more sense after that responsibility is clear. ## Self-hosting fits an infrastructure team [#self-hosting-fits-an-infrastructure-team] Run Keenpix yourself when your team needs to control where the application and database run, or modify the AGPL-3.0 code. Self-hosting alone does not guarantee residency or compliance: source requests, CDN traffic, email, backups, and logs may still leave the selected network or region. Document those paths separately. This model makes sense when Docker, PostgreSQL backups, TLS, monitoring, and capacity planning are already routine work. There is no managed delivery charge in this model. There is still infrastructure cost. Cold transforms consume CPU, the disk cache needs space, Postgres needs backups, and the public endpoint needs abuse controls. Horizontal replicas also need an outer CDN because their local disk caches are separate; bundled self-host mode does not currently provide a shared object-storage cache. The [Docker deployment guide](/blog/self-host-image-cdn-docker) covers a pinned release, health checks, cache boundaries, and the first database dump. Read it before estimating the operating cost from the container count alone. ## Managed cloud fits a product team [#managed-cloud-fits-a-product-team] Use the managed service when the team wants to create projects and deliver images without operating the database and transform workers. This removes a deployment from your on-call list, but it introduces a provider dependency and a bandwidth bill. Check the current [pricing page](/pricing) against the successful bytes you expect the managed network to deliver. Cloudflare edge hits, Keenpix cache hits, and new transforms each count once; browser hits and customer-owned CDN hits do not reach the managed network. Include expected growth and any regions or origins that need special handling. Do not compare a cloud plan only with the price of one self-hosted VM. Include backups, monitoring, upgrades, and the time spent responding to failures on that VM. ## Moving later is possible, but plan the details [#moving-later-is-possible-but-plan-the-details] Both editions understand the same core path and query parameters. A migration can still change the hostname, project identifier, signing secret, cache behavior, and DNS. Put the delivery hostname behind a configuration value from the beginning, and avoid scattering complete transform URLs through stored content. Test signed URLs and cache keys before switching traffic. A matching `?w=1200&fmt=webp` query does not prove that both deployments share the same project policy or signature. ## A practical decision [#a-practical-decision] Choose self-hosting when infrastructure control is a requirement and the team already owns the work it creates. Choose managed cloud when the image pipeline is supporting infrastructure that the product team does not want to operate. If the answer is still unclear, run the same source set through both for a week. Measure delivered bytes, cold-transform latency, cache hits, operator time, and failure recovery. That record is more useful than a generic cost calculator. ## Keep reading [#keep-reading] * [How the Keenpix managed-delivery meter works](/blog/transparent-image-cdn-pricing), including its cache boundary. * [Keenpix vs Cloudinary](/compare/cloudinary-alternative), [imgix](/compare/imgix-alternative), and [ImageKit](/compare/imagekit-alternative) compare the managed option. --- Markdown path: https://keenpix.com/blog/signed-image-urls-hmac.md # Signed image URLs with HMAC: stop unauthorized transforms > Learn when an origin allowlist is enough, when HMAC signing helps, how Keenpix canonicalizes a transform request, and what secret rotation breaks. Canonical HTML: [https://keenpix.com/blog/signed-image-urls-hmac](https://keenpix.com/blog/signed-image-urls-hmac) Author: Raed Bahri Published: 2026-08-15 Last reviewed: 2026-08-15 An origin allowlist answers one question: which hosts may Keenpix fetch from? It does not decide who may create a new width, crop, quality, or cache key for an allowed image. That distinction matters on public transform URLs. Someone who knows the project id and an allowed source can keep changing harmless query parameters. The source remains valid, but each variation can force another cache lookup or transform. HMAC signing binds the source and the full transform query to a secret that stays on your server. ## The request that gets signed [#the-request-that-gets-signed] Keenpix builds one canonical message: ```text + "\n" + ``` The query contains every parameter except `sig`. Each `key=value` pair is sorted lexicographically and joined with `&`. The signature is an HMAC-SHA256 digest encoded as base64url without padding. Sorting is not cosmetic. These two queries describe the same transform: ```text project=p_123&w=800&fmt=webp fmt=webp&w=800&project=p_123 ``` They produce the same canonical payload for a self-hosted URL. Managed first-party and custom-domain URLs place the project identity differently; the signing helper below handles those shapes explicitly. Adding another parameter, changing `w`, or changing the source URL produces a different signature. ## Sign on the server [#sign-on-the-server] ```ts import { createHmac } from 'node:crypto' export function createSignedImageUrl({ baseUrl, delivery, params, projectId, secret, source, }: { baseUrl: string delivery: 'self-host' | 'managed' | 'custom-domain' params: URLSearchParams projectId?: string secret: string source: string }) { const publicQuery = new URLSearchParams(params) publicQuery.delete('project') publicQuery.delete('sig') const signedQuery = new URLSearchParams(publicQuery) if (delivery !== 'custom-domain') { if (!projectId) throw new Error('projectId is required') signedQuery.set('project', projectId) if (delivery === 'self-host') publicQuery.set('project', projectId) } const sortedQuery = [...signedQuery.entries()] .map(([key, value]) => `${key}=${value}`) .sort() .join('&') const signature = createHmac('sha256', secret) .update(`${source}\n${sortedQuery}`) .digest('base64url') publicQuery.set('sig', signature) let prefix = baseUrl if (delivery === 'managed') { if (!projectId) throw new Error('projectId is required') prefix = `${baseUrl}/p/${encodeURIComponent(projectId)}` } return `${prefix}/img/${encodeURIComponent(source)}?${publicQuery}` } ``` Call this in a server route, server component, backend, or build process. Do not prefix the secret with `NEXT_PUBLIC_`, embed it in a mobile application, or return it from a configuration endpoint. For self-hosting, use `delivery: 'self-host'`; `project` appears in both the public query and the signature. For `https://cdn.keenpix.com/p//...`, use `delivery: 'managed'`; the public query omits `project`, but the edge Worker injects the path id before verification, so the signing copy includes it. For a verified customer hostname, use `delivery: 'custom-domain'` and omit `projectId`; the hostname chooses the project and Keenpix removes any legacy `project` query before checking the signature. ## What a valid signature protects [#what-a-valid-signature-protects] When signatures are required for the project, a request without `sig` returns `403`. A request with a signature computed for another width or source also returns `403`. Self-hosted and managed first-party signatures bind the project id; custom-domain signatures are scoped by the verified delivery hostname instead. Keenpix verifies the signature before reading its internal transform cache. `sig` is not part of that internal cache key, so valid requests for the same source and parameters can reuse one transformed variant. An outer CDN is a separate boundary. It normally keys on the full public URL, including `sig`. Do not remove `sig` from an edge cache key unless that edge verifies the signature before its cache lookup. Otherwise, an unsigned request could receive a cached response without reaching Keenpix. This follows the HTTP cache-key model described in [RFC 9111](https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-cache-keys-with). Signing does not make a private image public, nor does it add authentication to the source server. Keenpix still needs to fetch the source, and the source host still needs to be allowed. If the original requires private headers that Keenpix does not possess, a signed transform URL does not solve that access problem. ## Common implementation mistakes [#common-implementation-mistakes] The failures are usually small: * signing a URL-encoded source while the request path contains the raw source expected by the signing rule; * including `sig` in the canonical query; * sorting only keys while losing duplicate values; * signing in the browser; * appending a tracking parameter after the signature was created. Build a test with a fixed secret, source, and parameter set. Assert that parameter order does not change the digest. Then assert that changing one value does. ## Rotation has an origin and a cache boundary [#rotation-has-an-origin-and-a-cache-boundary] Rotating the project secret makes Keenpix reject the previous secret as soon as a request reaches the origin. A CDN or browser may still serve an immutable response it already cached, without asking Keenpix to verify the URL again. Purge cached signed URLs before treating an emergency rotation as complete; a browser may retain an image it has already downloaded. Plan the rollout in this order: 1. deploy code that can use the new secret; 2. rotate the project secret; 3. redeploy or revalidate pages that contain signed URLs; 4. purge signed image URLs from outer caches, check `403` rates, and remove the old secret from deployment systems. Keenpix stores one active signing secret per project. There is no overlap window between old and new values. Signatures also have no built-in expiration, so they do not stop replay of an existing valid URL. Treat rotation as a breaking credential change and cache purge, not as revocation of bytes already stored by a client. ## When the allowlist is enough [#when-the-allowlist-is-enough] Do not add signatures by habit. A public documentation site with a controlled image origin and an outer CDN may be adequately protected by its allowlist and normal traffic controls. Signing adds a server dependency to URL generation and complicates static pages during rotation. Use it when transform abuse has a real cost: high traffic, user-controlled sources, many expensive variants, or evidence that someone is manufacturing cache misses. Signing controls creation of transform requests; it is not access control for a private image and does not bind a valid URL to one website. ## Sources and verification [#sources-and-verification] This article was checked on August 15, 2026 against the [Keenpix signed URL documentation](/docs/concepts/signed-urls), the public [signature implementation](https://github.com/lord007tn/keenpix/blob/master/src/lib/transform-signing/signing.ts), and its parameter-order and tampering tests. It describes the current signing contract and should be treated as versioned API behavior. --- Markdown path: https://keenpix.com/blog/transparent-image-cdn-pricing.md # Image CDN Pricing: Why Keenpix Bills by Managed Delivery > See why Keenpix bills for optimized managed delivery instead of transformations, with published plans, included transforms, and always-on metered overage. Canonical HTML: [https://keenpix.com/blog/transparent-image-cdn-pricing](https://keenpix.com/blog/transparent-image-cdn-pricing) Author: Raed Bahri Published: 2026-06-28 Last reviewed: 2026-09-03 Image CDN bills can be hard to predict when several usage dimensions share one meter. Keenpix uses a narrower model: **optimized bytes delivered through managed cloud**, with transformations and team members included. ## The problem with per-transform and credit pricing [#the-problem-with-per-transform-and-credit-pricing] When a CDN charges per transformation — or bundles transforms, storage, and delivery into "credits" — your bill becomes coupled to implementation details you do not control: * A responsive `srcset` with eight breakpoints generates eight derived images. * Adding a modern AVIF variant alongside WebP doubles the transform count. * A crawler or a hotlinker can hammer new variants you never intended to create. None of that reflects real value delivered to real users. It just makes the meter spin. The rational response — optimize *less* to save money — is exactly backwards for a product whose whole job is optimization. ## What we meter instead [#what-we-meter-instead] Keenpix's billing meter records one thing: **optimized bytes delivered to end users through the managed Keenpix network**. A Cloudflare edge hit, a Keenpix optimized-variant-cache hit, and a newly optimized response from the origin each count once; the source changes, but the returned bytes remain one delivered response. Creating another transform does not create a separate charge. * **Transformations are free and unlimited.** Build as many variants as you want. * **One linear overage rate.** Published, per-GB, no pooled-credit math. * **Always-on paid delivery.** Usage and projected overage stay visible while production images continue serving. ## What caching changes [#what-caching-changes] Managed Cloudflare delivery is part of Keenpix, so its successful edge responses count toward the same delivery allowance as application responses. They are not added twice: an edge hit is counted at the edge, while an edge miss is counted when the Keenpix application returns the image. A browser cache hit—or a hit in a customer-owned CDN placed in front of Keenpix—never reaches the managed network and is not counted. Bandwidth saved is different. Keenpix analytics compare source bytes with the smaller optimized response and report the reduction as `bytesSaved`. That savings value is never added to delivered bytes and never sent to Polar as billable usage. ### Cache-hit billing, case by case [#cache-hit-billing-case-by-case] | Where the request is satisfied | Reaches managed Keenpix delivery? | Counts as one managed delivery response? | | -------------------------------------------------- | --------------------------------- | ---------------------------------------- | | Browser memory or disk cache | No | No | | Service worker returning its own stored response | No | No | | Customer-owned CDN in front of Keenpix | No, on that CDN's hit | No | | Keenpix-managed Cloudflare edge | Yes | Yes, once at the edge | | Keenpix optimized-variant cache after an edge miss | Yes | Yes, once when Keenpix returns the image | | New transform after all managed caches miss | Yes | Yes, once when Keenpix returns the image | The table describes Keenpix's current managed-meter boundary, verified against the repository on September 3, 2026. It is not a promise about charges from a browser vendor, customer-owned CDN, source-storage provider, or network carrier. An edge miss does not mean the same response is charged twice: the managed path records the successful delivery at the layer that returns it. Caching still matters to capacity and user experience even when the billing unit stays the same. Browser and customer-owned front-cache hits avoid the managed request entirely. Managed edge hits avoid origin fetch and transform work. Version image source URLs when bytes change so long-lived caches remain correct; the [cache invalidation and versioned URL guide](/blog/cache-invalidation-versioned-image-urls) covers that lifecycle. ## The result [#the-result] Optimizing aggressively — more formats, more breakpoints, higher quality where it matters — should reduce delivered bytes when the resulting files are smaller, not add a separate transformation fee. See the exact allowances and overage rates on [Keenpix pricing](/pricing), then model one workload across providers in the [image CDN cost calculator](/image-cdn-cost-calculator). And if you would rather not be billed at all, you can always [self-host Keenpix free](/docs/self-hosting) — the same open-source engine on your own infrastructure. ## See it against the incumbents [#see-it-against-the-incumbents] * [Keenpix vs Cloudinary](/compare/cloudinary-alternative) — credits vs managed-delivery pricing, with a reproducible canary method. * [Keenpix vs imgix](/compare/imgix-alternative) — credit packages vs managed-delivery billing, with a reproducible canary method. * [Keenpix vs ImageKit](/blog/keenpix-vs-imagekit) — two delivery-pricing models, compared. --- Markdown path: https://keenpix.com/blog/user-upload-image-pipeline-design.md # Design a user-upload image pipeline end to end > Design image uploads from authenticated ingestion and byte validation through quarantine, moderation, durable storage, transformation, delivery, and recovery. Canonical HTML: [https://keenpix.com/blog/user-upload-image-pipeline-design](https://keenpix.com/blog/user-upload-image-pipeline-design) Author: Raed Bahri Published: 2026-09-03 Last reviewed: 2026-09-03 A reliable user-upload image pipeline is a state machine, not a single upload endpoint. Accept bytes into quarantine, verify the authenticated actor and declared intent, inspect the actual file, store an immutable original, run moderation and processing asynchronously, and publish a new asset version only after every required gate passes. Delivery should read only published objects. Failed, rejected, and pending uploads must never share a public namespace with approved media. Keenpix can transform and deliver an image after your application publishes it at an allowed origin. It does not currently provide the upload API, source storage, virus scanning, moderation queue, or digital asset library described here. That boundary is important: adding an image CDN does not make untrusted uploads safe. ## Define the asset states before the endpoints [#define-the-asset-states-before-the-endpoints] Use explicit states that describe what consumers may do, rather than a loose set of booleans such as `uploaded`, `scanned`, and `approved`. | State | Meaning | Publicly deliverable? | | ---------------- | --------------------------------------------------------------- | --------------------- | | `initiated` | An authenticated actor requested an upload slot. | No | | `receiving` | Bytes may be arriving, but the object is incomplete. | No | | `quarantined` | The complete object exists in an isolated namespace. | No | | `validating` | Signature, decoder, dimension, and policy checks are running. | No | | `review_pending` | Automated checks passed; moderation still has to decide. | No | | `published` | A specific immutable version is approved for use. | Yes | | `rejected` | Policy or safety checks failed. | No | | `failed` | Processing failed for an operational reason and may be retried. | No | | `withdrawn` | A previously published version must no longer be selected. | No new references | Store the current state and an append-only transition history. Each transition should record the asset ID, version ID, actor or worker, reason code, policy version, and timestamp. Do not publish by moving a mutable object into place and hoping every cache notices. Publish by changing an application pointer from one immutable version to another. ## 1. Authorize intent before accepting bytes [#1-authorize-intent-before-accepting-bytes] The application should create an upload record before issuing a direct-upload grant. Check that the authenticated user can attach media to the target tenant, project, post, or product. Bind the grant to a generated asset ID, maximum byte length, short expiry, and narrow object key. Never let the browser choose an arbitrary bucket key or overwrite a published object. A useful initiation response contains: ```json { "assetId": "ast_01J...", "uploadId": "upl_01J...", "objectKey": "quarantine/tenant_42/ast_01J/original", "maxBytes": 10485760, "expiresAt": "2026-09-03T12:10:00Z", "uploadUrl": "https://storage.example/..." } ``` Treat the client-provided filename, extension, MIME type, dimensions, and checksum as hints. They can improve user feedback and idempotency, but none proves what the received bytes contain. The [OWASP File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html) recommends defense in depth: allowlisted extensions, generated filenames, actual content validation, size limits, authorization, storage outside the webroot, and malware or sandbox analysis where appropriate. For small files, proxying through the application can simplify policy enforcement. For larger files, a narrowly scoped direct-to-object-storage grant avoids tying up application workers. Both designs still need a trusted completion step. A browser saying “upload complete” is not evidence that storage received the expected bytes. ## 2. Finalize idempotently [#2-finalize-idempotently] On completion, the server should inspect storage metadata, compare the recorded object key and expected length, and enqueue validation exactly once. Require an idempotency key or make repeated completion calls converge on the same upload record. A retry after a network timeout must not create a second asset or publish the same asset twice. If multipart upload is enabled, expire abandoned parts and grants. Track `initiated` records that never reach quarantine, because unfinished uploads consume storage and can become an unbounded cost path. Do not expose the quarantine bucket through the application’s image host. ## 3. Validate bytes, not labels [#3-validate-bytes-not-labels] Validation should happen in an isolated worker with no application credentials and restricted network access. Start with cheap structural checks and stop early: 1. Enforce the streamed byte ceiling even when a declared content length exists. 2. Identify the format from magic bytes and decoder output, not only the filename or `Content-Type`. 3. Decode with hard pixel, page or frame, channel, memory, and time limits. 4. Reject truncated or malformed inputs instead of attempting a best-effort publish. 5. Remove or deliberately retain metadata according to product policy. 6. Generate a cryptographic digest for immutable identity and duplicate detection. 7. Run malware, steganography, or content-disarm checks when the threat model requires them. The Sharp constructor documents controls such as `limitInputPixels`, `unlimited`, page selection, and animated-image handling in its [input API](https://sharp.pixelplumbing.com/api-constructor/). Those options are safety inputs, not a complete sandbox. Codec vulnerabilities, pathological animation, large intermediate buffers, and native-library crashes still require patched dependencies and process-level CPU and memory limits. SVG needs a separate policy. It is active XML content, not merely a raster image with a different extension. Either reject it, sanitize it with a maintained policy, or render it in a strongly isolated process before publishing a raster derivative. Never serve an untrusted original SVG inline under your primary application origin. The remote-fetch side has a related threat model. If uploads may reference a URL instead of sending bytes, follow the complete [SSRF and image-bomb defense sequence](/blog/secure-image-pipelines-ssrf-image-bombs): allowlist protocols and hosts, reject private addresses, pin the validated connection, revalidate redirects, and enforce streamed limits. ## 4. Separate durable originals from public derivatives [#4-separate-durable-originals-from-public-derivatives] Keep the immutable original in a private source-of-truth namespace. Give each accepted replacement a new version ID or digest. Object-store versioning can protect against accidental overwrites, but it does not replace application-level state, retention, or authorization. Amazon S3, for example, assigns version IDs to object versions and stores versions as full objects rather than diffs; see the [S3 versioning workflow](https://docs.aws.amazon.com/AmazonS3/latest/userguide/versioning-workflows.html). A practical key layout is: ```text quarantine/{tenantId}/{assetId}/{uploadId} originals/{tenantId}/{assetId}/{versionId}/source published/{tenantId}/{assetId}/{versionId}/source derivatives/{tenantId}/{assetId}/{versionId}/{transformKey} ``` The names are examples, not a requirement. The invariant is that a pending upload cannot overwrite the bytes addressed by an already published URL. Apply tenant-scoped authorization to every database query and storage operation. An object key containing a tenant ID is not, by itself, tenant isolation. Encrypt storage and transport, restrict worker identities to the smallest necessary prefixes, and define retention. Decide how long to retain rejected objects, failed jobs, old published versions, moderation evidence, and deletion tombstones. “Keep everything” creates privacy and cost liabilities; “delete immediately” can destroy the evidence needed for appeals or incident response. ## 5. Make moderation an explicit gate [#5-make-moderation-an-explicit-gate] File safety and content acceptability are different decisions. A valid JPEG can still violate product policy; an animated GIF can be safe content but exceed a processing budget. Model moderation separately from decoding. Automated classifiers should return labels, confidence, model or policy version, and an outcome such as approve, reject, or escalate. Keep thresholds product-specific. A marketplace product photo, profile image, medical upload, and private enterprise attachment do not share one defensible moderation rule. Human review needs least-privilege access, an audit trail, an appeal path, and safe rendering. Do not ask a reviewer’s browser to open the untrusted original directly. Generate a bounded review derivative in isolation, and avoid exposing unrelated tenant identifiers in review tools. Animation deserves its own decision. Validate frame count, total decoded pixels across frames, duration, loop behavior, and work budget. Decide whether moderation evaluates a representative frame, every frame, or a sampled timeline. A first-frame-only check can miss later content. If animated delivery is unnecessary, flatten or reject animation at ingestion rather than letting each downstream transform rediscover that policy. ## 6. Publish atomically [#6-publish-atomically] Publishing should be a database transaction or an equivalently reliable workflow: 1. Confirm the asset version is still the expected candidate and every required gate passed. 2. Write or verify the immutable published object. 3. Change the asset’s `currentVersionId` pointer. 4. Record the transition and an outbox event in the same transaction. 5. Let consumers invalidate application data or refresh search indexes from the outbox. Do not mark the database row published before the object is readable, and do not copy bytes into a mutable public key after changing the pointer. Readers should see the old complete version or the new complete version, never a partially published state. The public URL should contain the version identity: ```text https://media.example.com/assets/ast_01J/v_7/source.jpg ``` That lets long-lived caches store published bytes safely. When a replacement is approved, the application emits a different URL. The [versioned image URL guide](/blog/cache-invalidation-versioned-image-urls) explains browser, CDN, and origin behavior in detail. ## 7. Transform and deliver from the published boundary [#7-transform-and-deliver-from-the-published-boundary] Generate a small mandatory derivative set during publication when the product cannot tolerate first-view transform latency. Generate the wider responsive ladder on demand only if the transformer has request bounds, admission control, and a finite cache-key vocabulary. Arbitrary width, quality, crop, and format parameters can turn one upload into an unbounded compute and storage surface. Use intrinsic `width` and `height`, meaningful alternative text stored alongside the asset reference, and a finite `srcset`. The [responsive image guide](/blog/responsive-image-cdn-guide) covers markup; the [cache-key guide](/blog/image-cdn-cache-keys-vary-accept) covers negotiated formats and complete variant identity. Keenpix fits here after publication. Point a project at the published origin, constrain that origin, and request versioned source URLs through the transform endpoint. Review the [bring-your-own-origin architecture](/blog/bring-your-own-origin-image-cdn-architecture) and [private-origin boundaries](/blog/private-image-origins-security-boundaries) before deciding whether source objects are public, signed, or network-private. Keenpix currently does not moderate uploads or decide whether an asset may be published. ## 8. Design every failure as a state transition [#8-design-every-failure-as-a-state-transition] Separate user errors, policy rejections, transient infrastructure failures, and permanent processing failures. | Failure | Response | Retry policy | | ------------------------------- | ---------------------------------------------------------- | ---------------------------------------- | | Grant expired before upload | Issue a new grant for the same draft asset. | User-initiated | | Byte or pixel ceiling exceeded | Reject with a stable reason code. | No automatic retry | | Decoder crash or timeout | Keep quarantined; retry in a fresh worker within a budget. | Bounded with backoff | | Malware or policy rejection | Move to `rejected`; retain per policy. | Appeal or new upload | | Moderation provider unavailable | Remain `review_pending`; do not fail open. | Backoff and alert | | Publish copy failed | Keep the old current version selected. | Idempotent retry | | Derivative generation failed | Preserve the original and record derivative status. | Format-specific retry or fallback policy | Use a dead-letter queue only with an operator workflow. A dead-letter bucket no one watches is silent data loss. Retries should carry the same asset and version identifiers, enforce a maximum attempt count, and avoid repeating non-idempotent side effects. Return safe errors to users. Logs may include internal reason codes, object versions, decoder versions, and correlation IDs, but should not contain presigned URLs, authentication headers, raw private filenames, or prohibited-content thumbnails. ## Observability and privacy [#observability-and-privacy] Measure each stage separately: bytes accepted, validation latency, rejection reason, scan latency, moderation queue age, publish latency, derivative failure rate, and delivery errors. Break results down by format and bounded size class, not by dumping sensitive object keys into a metrics label. Alert on state age as well as error counts. A quiet queue can be healthy or completely stalled. Useful service-level indicators include the percentage of eligible uploads published within the target time and the number of records stuck beyond the maximum expected stage duration. Deletion is also a workflow. Remove the application reference first, stop issuing new URLs, revoke private authorization where applicable, and schedule source, derivative, cache, backup, and audit-data treatment according to policy. A CDN purge cannot prove that every browser or downstream cache erased a previously public response. For highly sensitive media, private authorization and short freshness windows are more honest controls than a public immutable URL. ## Verification checklist [#verification-checklist] Test with more than friendly JPEGs: * a valid JPEG whose extension and MIME label claim PNG; * a truncated image and a file with a valid-looking header plus invalid body; * maximum bytes, maximum dimensions, one pixel over each limit, and extreme aspect ratios; * transparent, color-profiled, rotated, animated, and multi-page inputs; * an SVG with scripts, external references, and oversized paths; * repeated finalize calls, worker crashes, duplicate queue delivery, and publish retries; * cross-tenant object IDs and expired upload grants; * moderation timeout, rejection, appeal, withdrawal, and replacement; * old and new asset versions requested through browser and CDN caches. Run the security cases in an isolated environment. Then exercise the full delivery path using the [symptom-based troubleshooting guide](/blog/image-delivery-troubleshooting-by-symptom) and a [safe rollout and rollback plan](/blog/safe-image-cdn-rollouts-and-rollbacks). Record which assertions came from unit tests, object-store behavior, live HTTP responses, and a rendered browser; they prove different boundaries. ## The durable design rule [#the-durable-design-rule] The simplest safe mental model is: **untrusted bytes enter quarantine; only an immutable, approved version leaves it**. Authentication decides who may propose an asset. Validation decides whether the bytes are processable. Moderation decides whether the content is acceptable. Publication decides which version applications reference. Transformation and delivery optimize that already-published version. Keeping those decisions separate makes retries, replacement, moderation appeals, cache behavior, and incident response understandable. It also keeps the Keenpix integration honest: Keenpix can be the bounded transformation and delivery layer, while your application remains responsible for upload authority, source lifecycle, and content policy. --- Markdown path: https://keenpix.com/blog/what-is-an-image-cdn.md # What is an image CDN? How optimization and delivery work > An image CDN fetches, transforms, caches, and delivers image variants close to users. Learn the request lifecycle, tradeoffs, and when you need one. Canonical HTML: [https://keenpix.com/blog/what-is-an-image-cdn](https://keenpix.com/blog/what-is-an-image-cdn) Author: Raed Bahri Published: 2026-07-11 Last reviewed: 2026-08-31 An **image CDN is a delivery service that creates the right image variant for each request and caches that result near the visitor**. It combines an image transformation engine with CDN caching. Instead of preparing every width and format during a build, you request a URL that describes the output—such as an 800-pixel-wide WebP—and the service generates and reuses that variant. That definition matters because a traditional CDN and an image CDN solve related but different problems. A traditional CDN caches the file you give it. An image CDN can resize, crop, compress, and convert the source before caching the result. The two layers are often used together. ## The image CDN request lifecycle [#the-image-cdn-request-lifecycle] A typical request follows five steps: 1. **The page selects a candidate.** HTML such as `srcset` and `sizes` lets the browser choose an appropriate display width. The browser's `Accept` header can also declare support for formats such as AVIF or WebP. 2. **The image URL names a transformation.** Width, height, fit, quality, or format appears in the path or query string. 3. **The service validates the source.** A secure implementation checks the requested origin against an allowlist and prevents requests to private network addresses. Signed URLs may restrict which transformations callers can create. 4. **A cache miss triggers processing.** The service fetches the source, decodes it, applies the requested operations, and encodes the output. 5. **The result is cached and delivered.** Later requests for the same normalized transformation can reuse the generated variant. An edge CDN can cache it again closer to users. The [web.dev image CDN guide](https://web.dev/articles/image-cdns) describes the same basic split: transformation happens according to URL parameters, while a CDN reduces delivery distance and repeat work. Exact behavior varies by provider, so cache keys and invalidation rules belong in any production evaluation. ## What does “CDN for images” mean in practice? [#what-does-cdn-for-images-mean-in-practice] People searching for a **CDN for images** may mean three different things: a general CDN carrying prebuilt image files, an image transformation service behind an existing CDN, or a managed image CDN that bundles both responsibilities. The URL alone does not reveal which architecture is in use. Trace one cache miss to identify the boundary. Ask which system validates the source, creates the requested width and format, stores the generated variant, answers the next request, and records billable usage. A general CDN may answer only the repeat-delivery step. A focused transform engine may create the variant but leave edge delivery and observability to your team. A managed image CDN may combine those steps under one product and bill. This distinction prevents two common purchasing mistakes. The first is comparing a CDN bandwidth price with a managed image-service price while ignoring transformation, cache, and operations. The second is treating a free self-hosted license as a complete delivery cost while omitting compute, egress, monitoring, upgrades, and incident response. ## What does an image CDN optimize? [#what-does-an-image-cdn-optimize] The common operations are straightforward: * resize to the rendered dimensions instead of sending a desktop-sized image to a narrow screen; * convert to a supported modern format; * tune encoding quality for the content and use case; * crop or fit an image into a known aspect ratio; * strip metadata that is not needed for delivery; * cache each normalized result so processing is not repeated on every request. These operations can reduce transferred bytes, but no responsible provider can promise one universal percentage. Savings depend on the source format, source quality, requested dimensions, visual content, target format, and browser support. A small, already-optimized source may barely shrink. An oversized photographic PNG may shrink substantially. Publish measurements only with the input files and settings needed to reproduce them. ## Image CDN vs. build-time image optimization [#image-cdn-vs-build-time-image-optimization] Build-time tools create image variants while an application is built. They are a good fit when the image set is known, small enough to process, and deployed with the site. They keep runtime processing out of the request path. An image CDN is more useful when images arrive after deployment, live on remote origins, vary by user or catalog entry, or need many responsive widths. It trades build simplicity for a runtime dependency that must be secured, cached, monitored, and budgeted. Many systems combine both approaches. Static marketing assets can be generated at build time, while user content and large catalogs flow through an image CDN. ## Do you still need responsive HTML? [#do-you-still-need-responsive-html] Yes. An image CDN can create a 480-pixel and a 1280-pixel variant, but the browser still needs enough information to choose between them. The [MDN responsive images guide](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Responsive_images) explains how `srcset` and `sizes` provide width candidates and layout hints. ```html A product dashboard showing image delivery analytics ``` The `width` and `height` attributes reserve layout space. They do not force the browser to download the largest candidate. The alt text describes meaningful editorial content; decorative images should use an empty alt value instead. ## What an image CDN does not fix [#what-an-image-cdn-does-not-fix] An image CDN is not a complete web-performance strategy. It will not repair render-blocking CSS, excessive JavaScript, a slow document response, missing image dimensions, or an incorrect above-the-fold loading priority. It can also add latency on a cold transformation if the source is slow or the cache topology is poorly designed. Treat the service as one part of a delivery system. Measure browser-visible outcomes, cache behavior, error rates, and bytes—not only transformation speed in isolation. ## Security and operational questions to ask [#security-and-operational-questions-to-ask] Before putting a service in the image path, ask: * Which origin hosts can a project fetch from? * Are private and link-local addresses blocked after DNS resolution and redirects? * Can transformations be signed or restricted? * What becomes part of the cache key? * How are source changes invalidated? * What happens when usage or spending reaches a limit? * Are original URLs, query strings, or request headers retained in logs? * Can you move the workload or self-host if the service no longer fits? The answer should be product documentation, a configuration you can inspect, or a reproducible test—not an unsupported security badge. ## When should you use an image CDN? [#when-should-you-use-an-image-cdn] An image CDN is usually worth evaluating when you serve a changing image catalog, support multiple screen sizes, pull from origins you already operate, or need one transformation grammar across frameworks. It may be unnecessary for a small static site whose build already emits correct formats and responsive widths. For the category distinction, continue with [image CDN vs traditional CDN](/blog/image-cdn-vs-traditional-cdn). For implementation, see the [responsive image CDN guide](/blog/responsive-image-cdn-guide). If you are choosing between infrastructure ownership and a service, use the [self-hosted vs managed decision framework](/blog/self-host-vs-managed-image-optimization). To compare public pricing boundaries with your own delivery inputs, use the [image CDN cost calculator](/image-cdn-cost-calculator), then run the source-dated [Cloudinary](/compare/cloudinary-alternative) or [imgix](/compare/imgix-alternative) canary checklist against your own images. ## Sources and verification [#sources-and-verification] This explainer was last reviewed on August 22, 2026 against [web.dev's image CDN guidance](https://web.dev/articles/image-cdns), [MDN's responsive images guide](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Responsive_images), and the [HTTP `Accept` header reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept). Examples are illustrative; they are not benchmark results.