Vibecode Cloudinary
track this build5 phases, 10 steps, beginner friendly0%On-the-fly resizing and format negotiation is imgproxy or a sharp service in front of object storage, and a weekend gets you there. The global CDN, video transcoding and the media library UI are what $99 buys, and the CDN is not something you run.
You are building a lean indie version of Cloudinary. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Cloudinary · indie build An image CDN for your own sites without writing an image processor: originals in S3-compatible storage, imgproxy resizing and converting on request by signed URL, a cache in front, and a helper that emits srcset for every image. Video and the media library stay with the vendor. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Processor | imgproxy in Docker | rule zero: do not write an image processor | | Storage | Cloudflare R2, Backblaze B2 or MinIO | originals as objects, keyed by content hash | | Cache | Cloudflare or Caddy | transforms are cheap once, expensive always | | Upload service | A small Node endpoint | sniff, cap, strip, store | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **An S3-compatible bucket with access keys** · cents to a few dollars a month - Why: Where originals live. - Get it: Cloudflare R2: dashboard > R2 > Create bucket > Manage R2 API Tokens. Backblaze B2: Buckets > Create, then App Keys. Note endpoint, bucket, key id and secret. - [ ] **Docker on a VPS** · free - Why: imgproxy runs as a container. - Get it: docs.docker.com/engine/install/ubuntu - Verify: docker compose version prints - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. imgproxy needs a box; 2 GB of RAM is comfortable. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: img.yourdomain.com - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server - [ ] **imgproxy signing key and salt** · free - Why: Unsigned URLs would let anyone resize anything through your box. - Get it: openssl rand -hex 32 twice, into .env. ## Quick start ```sh mkdir imgcdn && cd imgcdn && git init && npm init -y && npm pkg set type=module && npm install @aws-sdk/client-s3@3 sharp@0.35.3 mkdir -p data && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Video, the media library, AI tagging, upload widgets: the $99. - video transcoding and adaptive streaming - the media library and AI tagging - global edge delivery you do not operate - upload widgets and SDKs for every platform If one of those is essential to you, that is the reason to keep paying for Cloudinary, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Cloudinary The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an image CDN like Cloudinary for images only. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write an image processor. imgproxy is one binary that resizes, crops and converts safely; you build the storage and the caching around it. ### Stack (fixed, do not substitute) - S3-compatible object storage for originals (Cloudflare R2, Backblaze B2 or MinIO). imgproxy in Docker. Caddy or Cloudflare in front for caching. A small Node upload service. ### Phase 1 · Originals Build: an upload endpoint behind a token that stores the original in the bucket under a content hash, sniffs the type, caps the size, strips EXIF, and returns the key. Nothing is ever overwritten. Done when: two uploads of the same file store once, a renamed executable is refused, and the stored file has no GPS EXIF. Do not build yet: transforms. ### Phase 2 · Transforms Build: imgproxy configured with the bucket as source, signed URLs with a key and salt from .env, and a helper that builds img(key, width, height, format) URLs. Auto WebP and AVIF by Accept header. Done when: one original serves at three widths in the format the browser prefers and an unsigned URL is refused. ### Phase 3 · Cache Build: long cache headers on transformed responses, a CDN or Caddy cache in front, and a purge command by key. Done when: the second request for a transform is served by the cache and purge makes the next one miss. ### Phase 4 · Responsive helper Build: a function producing a srcset and sizes for a key, and a build-time step that replaces plain img tags in your site with it. Done when: a page loads the smallest sufficient image on a phone and the largest on a desktop. ### Phase 5 · Operate Build: bucket lifecycle rules, imgproxy memory limits, /healthz, backups of the key index, the README. Done when: imgproxy survives a 50 MB hostile image without falling over and the README goes from zero to a served image. ### Out of scope (and why) - Video, the media library, AI tagging, upload widgets. That is the $99. ### README must contain - The signing scheme and what happens without it. - Where originals live and how to restore them. ===== AGENTS.md ===== # Agent instructions · Cloudinary indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: imgproxy in Docker, Cloudflare R2, Backblaze B2 or MinIO, Cloudflare or Caddy, A small Node endpoint. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Cloudinary An image CDN for your own sites without writing an image processor: originals in S3-compatible storage, imgproxy resizing and converting on request by signed URL, a cache in front, and a helper that emits srcset for every image. Video and the media library stay with the vendor. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Originals Upload behind a token, content-hash keys, sniffed, capped, EXIF stripped, never overwritten. ### Steps 1. The upload endpoint with sharp for sniffing and EXIF stripping ```sh mkdir imgcdn && cd imgcdn && git init && npm init -y && npm pkg set type=module && npm install @aws-sdk/client-s3@3 sharp@0.35.3 mkdir -p data && cp .env.example .env ``` 2. Store under sha256 of the bytes; refuse non-images and files over the cap ### Done when - [ ] Two uploads of the same file store once - [ ] A renamed executable is refused - [ ] The stored file has no GPS EXIF ## Phase 2 · Transforms imgproxy with the bucket as source, signed URLs, auto WebP and AVIF. ### Steps 1. docker-compose.yml for imgproxy with S3 source and signing enabled Files: `docker-compose.yml` 2. A helper img(key, width, height, format) building signed URLs ### Done when - [ ] One original serves at three widths in the browser's preferred format - [ ] An unsigned URL is refused ## Phase 3 · Cache Long cache headers, a cache in front, purge by key. ### Steps 1. Cache-Control on transforms; Cloudflare or Caddy cache 2. A purge command by key ### Done when - [ ] The second request is served by the cache - [ ] Purge makes the next request miss ## Phase 4 · Responsive helper srcset and sizes for every image in your site. ### Steps 1. A function producing srcset and sizes 2. A build step replacing plain img tags in your site ### Done when - [ ] A phone loads the smallest sufficient image; a desktop the largest ## Phase 5 · Operate Lifecycle rules, memory limits, healthz, backups of the key index. ### Steps 1. imgproxy memory limits and /healthz; bucket lifecycle rules 2. README with the signing scheme and where originals live Files: `README.md` ### Done when - [ ] imgproxy survives a 50 MB hostile image - [ ] The README reaches a served image ## Not in this build - Video, the media library, AI tagging, upload widgets: the $99. ## After v1, if you want it - Direct-to-bucket presigned uploads from the browser - Focal-point cropping metadata ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. From the bucket provider. S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com # Required. Bucket name. S3_BUCKET=images # Required · secret. Bucket access key. S3_ACCESS_KEY_ID=... # Required · secret. Bucket secret. S3_SECRET_ACCESS_KEY=... # Required · secret. openssl rand -hex 32. IMGPROXY_KEY=hex # Required · secret. openssl rand -hex 32. IMGPROXY_SALT=hex # Required · secret. Bearer token for the upload endpoint. UPLOAD_TOKEN=base64 # Required. Public base of the image domain. IMG_URL=https://img.yourdomain.com
You are building a lean indie version of Cloudinary. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Cloudinary · indie build An image CDN for your own sites without writing an image processor: originals in S3-compatible storage, imgproxy resizing and converting on request by signed URL, a cache in front, and a helper that emits srcset for every image. Video and the media library stay with the vendor. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Processor | imgproxy in Docker | rule zero: do not write an image processor | | Storage | Cloudflare R2, Backblaze B2 or MinIO | originals as objects, keyed by content hash | | Cache | Cloudflare or Caddy | transforms are cheap once, expensive always | | Upload service | A small Node endpoint | sniff, cap, strip, store | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **An S3-compatible bucket with access keys** · cents to a few dollars a month - Why: Where originals live. - Get it: Cloudflare R2: dashboard > R2 > Create bucket > Manage R2 API Tokens. Backblaze B2: Buckets > Create, then App Keys. Note endpoint, bucket, key id and secret. - [ ] **Docker on a VPS** · free - Why: imgproxy runs as a container. - Get it: docs.docker.com/engine/install/ubuntu - Verify: docker compose version prints - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. imgproxy needs a box; 2 GB of RAM is comfortable. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: img.yourdomain.com - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server - [ ] **imgproxy signing key and salt** · free - Why: Unsigned URLs would let anyone resize anything through your box. - Get it: openssl rand -hex 32 twice, into .env. ## Quick start ```sh mkdir imgcdn && cd imgcdn && git init && npm init -y && npm pkg set type=module && npm install @aws-sdk/client-s3@3 sharp@0.35.3 mkdir -p data && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Video, the media library, AI tagging, upload widgets: the $99. - video transcoding and adaptive streaming - the media library and AI tagging - global edge delivery you do not operate - upload widgets and SDKs for every platform If one of those is essential to you, that is the reason to keep paying for Cloudinary, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Cloudinary The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an image CDN like Cloudinary for images only. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write an image processor. imgproxy is one binary that resizes, crops and converts safely; you build the storage and the caching around it. ### Stack (fixed, do not substitute) - S3-compatible object storage for originals (Cloudflare R2, Backblaze B2 or MinIO). imgproxy in Docker. Caddy or Cloudflare in front for caching. A small Node upload service. ### Phase 1 · Originals Build: an upload endpoint behind a token that stores the original in the bucket under a content hash, sniffs the type, caps the size, strips EXIF, and returns the key. Nothing is ever overwritten. Done when: two uploads of the same file store once, a renamed executable is refused, and the stored file has no GPS EXIF. Do not build yet: transforms. ### Phase 2 · Transforms Build: imgproxy configured with the bucket as source, signed URLs with a key and salt from .env, and a helper that builds img(key, width, height, format) URLs. Auto WebP and AVIF by Accept header. Done when: one original serves at three widths in the format the browser prefers and an unsigned URL is refused. ### Phase 3 · Cache Build: long cache headers on transformed responses, a CDN or Caddy cache in front, and a purge command by key. Done when: the second request for a transform is served by the cache and purge makes the next one miss. ### Phase 4 · Responsive helper Build: a function producing a srcset and sizes for a key, and a build-time step that replaces plain img tags in your site with it. Done when: a page loads the smallest sufficient image on a phone and the largest on a desktop. ### Phase 5 · Operate Build: bucket lifecycle rules, imgproxy memory limits, /healthz, backups of the key index, the README. Done when: imgproxy survives a 50 MB hostile image without falling over and the README goes from zero to a served image. ### Out of scope (and why) - Video, the media library, AI tagging, upload widgets. That is the $99. ### README must contain - The signing scheme and what happens without it. - Where originals live and how to restore them. ===== AGENTS.md ===== # Agent instructions · Cloudinary indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: imgproxy in Docker, Cloudflare R2, Backblaze B2 or MinIO, Cloudflare or Caddy, A small Node endpoint. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Cloudinary An image CDN for your own sites without writing an image processor: originals in S3-compatible storage, imgproxy resizing and converting on request by signed URL, a cache in front, and a helper that emits srcset for every image. Video and the media library stay with the vendor. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Originals Upload behind a token, content-hash keys, sniffed, capped, EXIF stripped, never overwritten. ### Steps 1. The upload endpoint with sharp for sniffing and EXIF stripping ```sh mkdir imgcdn && cd imgcdn && git init && npm init -y && npm pkg set type=module && npm install @aws-sdk/client-s3@3 sharp@0.35.3 mkdir -p data && cp .env.example .env ``` 2. Store under sha256 of the bytes; refuse non-images and files over the cap ### Done when - [ ] Two uploads of the same file store once - [ ] A renamed executable is refused - [ ] The stored file has no GPS EXIF ## Phase 2 · Transforms imgproxy with the bucket as source, signed URLs, auto WebP and AVIF. ### Steps 1. docker-compose.yml for imgproxy with S3 source and signing enabled Files: `docker-compose.yml` 2. A helper img(key, width, height, format) building signed URLs ### Done when - [ ] One original serves at three widths in the browser's preferred format - [ ] An unsigned URL is refused ## Phase 3 · Cache Long cache headers, a cache in front, purge by key. ### Steps 1. Cache-Control on transforms; Cloudflare or Caddy cache 2. A purge command by key ### Done when - [ ] The second request is served by the cache - [ ] Purge makes the next request miss ## Phase 4 · Responsive helper srcset and sizes for every image in your site. ### Steps 1. A function producing srcset and sizes 2. A build step replacing plain img tags in your site ### Done when - [ ] A phone loads the smallest sufficient image; a desktop the largest ## Phase 5 · Operate Lifecycle rules, memory limits, healthz, backups of the key index. ### Steps 1. imgproxy memory limits and /healthz; bucket lifecycle rules 2. README with the signing scheme and where originals live Files: `README.md` ### Done when - [ ] imgproxy survives a 50 MB hostile image - [ ] The README reaches a served image ## Not in this build - Video, the media library, AI tagging, upload widgets: the $99. ## After v1, if you want it - Direct-to-bucket presigned uploads from the browser - Focal-point cropping metadata ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. From the bucket provider. S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com # Required. Bucket name. S3_BUCKET=images # Required · secret. Bucket access key. S3_ACCESS_KEY_ID=... # Required · secret. Bucket secret. S3_SECRET_ACCESS_KEY=... # Required · secret. openssl rand -hex 32. IMGPROXY_KEY=hex # Required · secret. openssl rand -hex 32. IMGPROXY_SALT=hex # Required · secret. Bearer token for the upload endpoint. UPLOAD_TOKEN=base64 # Required. Public base of the image domain. IMG_URL=https://img.yourdomain.com
You are building a production product version of Cloudinary. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Cloudinary · product brief ## Problem On-the-fly resizing and format negotiation is imgproxy or a sharp service in front of object storage, and a weekend gets you there. The global CDN, video transcoding and the media library UI are what $99 buys, and the CDN is not something you run. ## Product outcome Image delivery for all your sites at object-storage prices, with video honestly left out. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - object storage (S3-compatible) - a VPS or container for the transformer - a CDN or Cloudflare in front ## Explicit non-goals for v1 - Video, the media library, AI tagging, upload widgets: the $99. - video transcoding and adaptive streaming - the media library and AI tagging - global edge delivery you do not operate - upload widgets and SDKs for every platform ## Success criteria - Signing verified - Hostile image test passes - Bucket versioning or replication on ===== BRIEF.md ===== # Build brief · Cloudinary The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an image CDN like Cloudinary for images only. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write an image processor. imgproxy is one binary that resizes, crops and converts safely; you build the storage and the caching around it. ### Stack (fixed, do not substitute) - S3-compatible object storage for originals (Cloudflare R2, Backblaze B2 or MinIO). imgproxy in Docker. Caddy or Cloudflare in front for caching. A small Node upload service. ### Phase 1 · Originals Build: an upload endpoint behind a token that stores the original in the bucket under a content hash, sniffs the type, caps the size, strips EXIF, and returns the key. Nothing is ever overwritten. Done when: two uploads of the same file store once, a renamed executable is refused, and the stored file has no GPS EXIF. Do not build yet: transforms. ### Phase 2 · Transforms Build: imgproxy configured with the bucket as source, signed URLs with a key and salt from .env, and a helper that builds img(key, width, height, format) URLs. Auto WebP and AVIF by Accept header. Done when: one original serves at three widths in the format the browser prefers and an unsigned URL is refused. ### Phase 3 · Cache Build: long cache headers on transformed responses, a CDN or Caddy cache in front, and a purge command by key. Done when: the second request for a transform is served by the cache and purge makes the next one miss. ### Phase 4 · Responsive helper Build: a function producing a srcset and sizes for a key, and a build-time step that replaces plain img tags in your site with it. Done when: a page loads the smallest sufficient image on a phone and the largest on a desktop. ### Phase 5 · Operate Build: bucket lifecycle rules, imgproxy memory limits, /healthz, backups of the key index, the README. Done when: imgproxy survives a 50 MB hostile image without falling over and the README goes from zero to a served image. ### Out of scope (and why) - Video, the media library, AI tagging, upload widgets. That is the $99. ### README must contain - The signing scheme and what happens without it. - Where originals live and how to restore them. ===== ARCHITECTURE.md ===== # Architecture · Cloudinary ## Stack | Part | Choice | Why | | --- | --- | --- | | Processor | imgproxy in Docker | rule zero: do not write an image processor | | Storage | Cloudflare R2, Backblaze B2 or MinIO | originals as objects, keyed by content hash | | Cache | Cloudflare or Caddy | transforms are cheap once, expensive always | | Upload service | A small Node endpoint | sniff, cap, strip, store | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Upload | the endpoint and originals | Direct presigned uploads later | | Processor | imgproxy | Thumbor or a sharp service | | Cache | headers and purge | Any CDN | | Helper | srcset generation | A framework image component | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `S3_ENDPOINT` · required · From the bucket provider. - `S3_BUCKET` · required · Bucket name. - `S3_ACCESS_KEY_ID` · required, secret · Bucket access key. - `S3_SECRET_ACCESS_KEY` · required, secret · Bucket secret. - `IMGPROXY_KEY` · required, secret · openssl rand -hex 32. - `IMGPROXY_SALT` · required, secret · openssl rand -hex 32. - `UPLOAD_TOKEN` · required, secret · Bearer token for the upload endpoint. - `IMG_URL` · required · Public base of the image domain. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Cloudinary product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: imgproxy in Docker, Cloudflare R2, Backblaze B2 or MinIO, Cloudflare or Caddy, A small Node endpoint. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ===== MILESTONES.md ===== # Delivery milestones · Cloudinary Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Originals Upload behind a token, content-hash keys, sniffed, capped, EXIF stripped, never overwritten. ### Steps 1. The upload endpoint with sharp for sniffing and EXIF stripping ```sh mkdir imgcdn && cd imgcdn && git init && npm init -y && npm pkg set type=module && npm install @aws-sdk/client-s3@3 sharp@0.35.3 mkdir -p data && cp .env.example .env ``` 2. Store under sha256 of the bytes; refuse non-images and files over the cap ### Done when - [ ] Two uploads of the same file store once - [ ] A renamed executable is refused - [ ] The stored file has no GPS EXIF ## M2 · Transforms imgproxy with the bucket as source, signed URLs, auto WebP and AVIF. ### Steps 1. docker-compose.yml for imgproxy with S3 source and signing enabled Files: `docker-compose.yml` 2. A helper img(key, width, height, format) building signed URLs ### Done when - [ ] One original serves at three widths in the browser's preferred format - [ ] An unsigned URL is refused ## M3 · Cache Long cache headers, a cache in front, purge by key. ### Steps 1. Cache-Control on transforms; Cloudflare or Caddy cache 2. A purge command by key ### Done when - [ ] The second request is served by the cache - [ ] Purge makes the next request miss ## M4 · Responsive helper srcset and sizes for every image in your site. ### Steps 1. A function producing srcset and sizes 2. A build step replacing plain img tags in your site ### Done when - [ ] A phone loads the smallest sufficient image; a desktop the largest ## M5 · Operate Lifecycle rules, memory limits, healthz, backups of the key index. ### Steps 1. imgproxy memory limits and /healthz; bucket lifecycle rules 2. README with the signing scheme and where originals live Files: `README.md` ### Done when - [ ] imgproxy survives a 50 MB hostile image - [ ] The README reaches a served image ## M6 · Serve several sites (production only) Per-site prefixes, tokens and metrics. ### Steps 1. Per-site key prefixes and upload tokens 2. Bandwidth and transform counts per site from the cache logs ### Done when - [ ] Two sites upload to separate prefixes - [ ] Per-site usage is visible ===== OPERATIONS.md ===== # Operations · Cloudinary ## Backup Originals are the asset: enable bucket versioning or replicate to a second bucket. ## Restore Point imgproxy at the copy. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on img domain and imgproxy memory. ## Incident checklist Rotate IMGPROXY_KEY; all old URLs stop, so regenerate. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Signing verified - [ ] Hostile image test passes - [ ] Bucket versioning or replication on ## Launch constraint Do not market omitted Cloudinary capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. From the bucket provider. S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com # Required. Bucket name. S3_BUCKET=images # Required · secret. Bucket access key. S3_ACCESS_KEY_ID=... # Required · secret. Bucket secret. S3_SECRET_ACCESS_KEY=... # Required · secret. openssl rand -hex 32. IMGPROXY_KEY=hex # Required · secret. openssl rand -hex 32. IMGPROXY_SALT=hex # Required · secret. Bearer token for the upload endpoint. UPLOAD_TOKEN=base64 # Required. Public base of the image domain. IMG_URL=https://img.yourdomain.com
# Cloudinary · indie build An image CDN for your own sites without writing an image processor: originals in S3-compatible storage, imgproxy resizing and converting on request by signed URL, a cache in front, and a helper that emits srcset for every image. Video and the media library stay with the vendor. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Processor | imgproxy in Docker | rule zero: do not write an image processor | | Storage | Cloudflare R2, Backblaze B2 or MinIO | originals as objects, keyed by content hash | | Cache | Cloudflare or Caddy | transforms are cheap once, expensive always | | Upload service | A small Node endpoint | sniff, cap, strip, store | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **An S3-compatible bucket with access keys** · cents to a few dollars a month - Why: Where originals live. - Get it: Cloudflare R2: dashboard > R2 > Create bucket > Manage R2 API Tokens. Backblaze B2: Buckets > Create, then App Keys. Note endpoint, bucket, key id and secret. - [ ] **Docker on a VPS** · free - Why: imgproxy runs as a container. - Get it: docs.docker.com/engine/install/ubuntu - Verify: docker compose version prints - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. imgproxy needs a box; 2 GB of RAM is comfortable. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: img.yourdomain.com - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server - [ ] **imgproxy signing key and salt** · free - Why: Unsigned URLs would let anyone resize anything through your box. - Get it: openssl rand -hex 32 twice, into .env. ## Quick start ```sh mkdir imgcdn && cd imgcdn && git init && npm init -y && npm pkg set type=module && npm install @aws-sdk/client-s3@3 sharp@0.35.3 mkdir -p data && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Video, the media library, AI tagging, upload widgets: the $99. - video transcoding and adaptive streaming - the media library and AI tagging - global edge delivery you do not operate - upload widgets and SDKs for every platform If one of those is essential to you, that is the reason to keep paying for Cloudinary, and the README should say so rather than pretend.
# Build brief · Cloudinary The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an image CDN like Cloudinary for images only. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write an image processor. imgproxy is one binary that resizes, crops and converts safely; you build the storage and the caching around it. ### Stack (fixed, do not substitute) - S3-compatible object storage for originals (Cloudflare R2, Backblaze B2 or MinIO). imgproxy in Docker. Caddy or Cloudflare in front for caching. A small Node upload service. ### Phase 1 · Originals Build: an upload endpoint behind a token that stores the original in the bucket under a content hash, sniffs the type, caps the size, strips EXIF, and returns the key. Nothing is ever overwritten. Done when: two uploads of the same file store once, a renamed executable is refused, and the stored file has no GPS EXIF. Do not build yet: transforms. ### Phase 2 · Transforms Build: imgproxy configured with the bucket as source, signed URLs with a key and salt from .env, and a helper that builds img(key, width, height, format) URLs. Auto WebP and AVIF by Accept header. Done when: one original serves at three widths in the format the browser prefers and an unsigned URL is refused. ### Phase 3 · Cache Build: long cache headers on transformed responses, a CDN or Caddy cache in front, and a purge command by key. Done when: the second request for a transform is served by the cache and purge makes the next one miss. ### Phase 4 · Responsive helper Build: a function producing a srcset and sizes for a key, and a build-time step that replaces plain img tags in your site with it. Done when: a page loads the smallest sufficient image on a phone and the largest on a desktop. ### Phase 5 · Operate Build: bucket lifecycle rules, imgproxy memory limits, /healthz, backups of the key index, the README. Done when: imgproxy survives a 50 MB hostile image without falling over and the README goes from zero to a served image. ### Out of scope (and why) - Video, the media library, AI tagging, upload widgets. That is the $99. ### README must contain - The signing scheme and what happens without it. - Where originals live and how to restore them.
# Agent instructions · Cloudinary indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: imgproxy in Docker, Cloudflare R2, Backblaze B2 or MinIO, Cloudflare or Caddy, A small Node endpoint. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs".
# Build plan · Cloudinary An image CDN for your own sites without writing an image processor: originals in S3-compatible storage, imgproxy resizing and converting on request by signed URL, a cache in front, and a helper that emits srcset for every image. Video and the media library stay with the vendor. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Originals Upload behind a token, content-hash keys, sniffed, capped, EXIF stripped, never overwritten. ### Steps 1. The upload endpoint with sharp for sniffing and EXIF stripping ```sh mkdir imgcdn && cd imgcdn && git init && npm init -y && npm pkg set type=module && npm install @aws-sdk/client-s3@3 sharp@0.35.3 mkdir -p data && cp .env.example .env ``` 2. Store under sha256 of the bytes; refuse non-images and files over the cap ### Done when - [ ] Two uploads of the same file store once - [ ] A renamed executable is refused - [ ] The stored file has no GPS EXIF ## Phase 2 · Transforms imgproxy with the bucket as source, signed URLs, auto WebP and AVIF. ### Steps 1. docker-compose.yml for imgproxy with S3 source and signing enabled Files: `docker-compose.yml` 2. A helper img(key, width, height, format) building signed URLs ### Done when - [ ] One original serves at three widths in the browser's preferred format - [ ] An unsigned URL is refused ## Phase 3 · Cache Long cache headers, a cache in front, purge by key. ### Steps 1. Cache-Control on transforms; Cloudflare or Caddy cache 2. A purge command by key ### Done when - [ ] The second request is served by the cache - [ ] Purge makes the next request miss ## Phase 4 · Responsive helper srcset and sizes for every image in your site. ### Steps 1. A function producing srcset and sizes 2. A build step replacing plain img tags in your site ### Done when - [ ] A phone loads the smallest sufficient image; a desktop the largest ## Phase 5 · Operate Lifecycle rules, memory limits, healthz, backups of the key index. ### Steps 1. imgproxy memory limits and /healthz; bucket lifecycle rules 2. README with the signing scheme and where originals live Files: `README.md` ### Done when - [ ] imgproxy survives a 50 MB hostile image - [ ] The README reaches a served image ## Not in this build - Video, the media library, AI tagging, upload widgets: the $99. ## After v1, if you want it - Direct-to-bucket presigned uploads from the browser - Focal-point cropping metadata
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. From the bucket provider. S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com # Required. Bucket name. S3_BUCKET=images # Required · secret. Bucket access key. S3_ACCESS_KEY_ID=... # Required · secret. Bucket secret. S3_SECRET_ACCESS_KEY=... # Required · secret. openssl rand -hex 32. IMGPROXY_KEY=hex # Required · secret. openssl rand -hex 32. IMGPROXY_SALT=hex # Required · secret. Bearer token for the upload endpoint. UPLOAD_TOKEN=base64 # Required. Public base of the image domain. IMG_URL=https://img.yourdomain.com
# Cloudinary · product brief ## Problem On-the-fly resizing and format negotiation is imgproxy or a sharp service in front of object storage, and a weekend gets you there. The global CDN, video transcoding and the media library UI are what $99 buys, and the CDN is not something you run. ## Product outcome Image delivery for all your sites at object-storage prices, with video honestly left out. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - object storage (S3-compatible) - a VPS or container for the transformer - a CDN or Cloudflare in front ## Explicit non-goals for v1 - Video, the media library, AI tagging, upload widgets: the $99. - video transcoding and adaptive streaming - the media library and AI tagging - global edge delivery you do not operate - upload widgets and SDKs for every platform ## Success criteria - Signing verified - Hostile image test passes - Bucket versioning or replication on
# Build brief · Cloudinary The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an image CDN like Cloudinary for images only. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write an image processor. imgproxy is one binary that resizes, crops and converts safely; you build the storage and the caching around it. ### Stack (fixed, do not substitute) - S3-compatible object storage for originals (Cloudflare R2, Backblaze B2 or MinIO). imgproxy in Docker. Caddy or Cloudflare in front for caching. A small Node upload service. ### Phase 1 · Originals Build: an upload endpoint behind a token that stores the original in the bucket under a content hash, sniffs the type, caps the size, strips EXIF, and returns the key. Nothing is ever overwritten. Done when: two uploads of the same file store once, a renamed executable is refused, and the stored file has no GPS EXIF. Do not build yet: transforms. ### Phase 2 · Transforms Build: imgproxy configured with the bucket as source, signed URLs with a key and salt from .env, and a helper that builds img(key, width, height, format) URLs. Auto WebP and AVIF by Accept header. Done when: one original serves at three widths in the format the browser prefers and an unsigned URL is refused. ### Phase 3 · Cache Build: long cache headers on transformed responses, a CDN or Caddy cache in front, and a purge command by key. Done when: the second request for a transform is served by the cache and purge makes the next one miss. ### Phase 4 · Responsive helper Build: a function producing a srcset and sizes for a key, and a build-time step that replaces plain img tags in your site with it. Done when: a page loads the smallest sufficient image on a phone and the largest on a desktop. ### Phase 5 · Operate Build: bucket lifecycle rules, imgproxy memory limits, /healthz, backups of the key index, the README. Done when: imgproxy survives a 50 MB hostile image without falling over and the README goes from zero to a served image. ### Out of scope (and why) - Video, the media library, AI tagging, upload widgets. That is the $99. ### README must contain - The signing scheme and what happens without it. - Where originals live and how to restore them.
# Architecture · Cloudinary ## Stack | Part | Choice | Why | | --- | --- | --- | | Processor | imgproxy in Docker | rule zero: do not write an image processor | | Storage | Cloudflare R2, Backblaze B2 or MinIO | originals as objects, keyed by content hash | | Cache | Cloudflare or Caddy | transforms are cheap once, expensive always | | Upload service | A small Node endpoint | sniff, cap, strip, store | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Upload | the endpoint and originals | Direct presigned uploads later | | Processor | imgproxy | Thumbor or a sharp service | | Cache | headers and purge | Any CDN | | Helper | srcset generation | A framework image component | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `S3_ENDPOINT` · required · From the bucket provider. - `S3_BUCKET` · required · Bucket name. - `S3_ACCESS_KEY_ID` · required, secret · Bucket access key. - `S3_SECRET_ACCESS_KEY` · required, secret · Bucket secret. - `IMGPROXY_KEY` · required, secret · openssl rand -hex 32. - `IMGPROXY_SALT` · required, secret · openssl rand -hex 32. - `UPLOAD_TOKEN` · required, secret · Bearer token for the upload endpoint. - `IMG_URL` · required · Public base of the image domain. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Cloudinary product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: imgproxy in Docker, Cloudflare R2, Backblaze B2 or MinIO, Cloudflare or Caddy, A small Node endpoint. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone.
# Delivery milestones · Cloudinary Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Originals Upload behind a token, content-hash keys, sniffed, capped, EXIF stripped, never overwritten. ### Steps 1. The upload endpoint with sharp for sniffing and EXIF stripping ```sh mkdir imgcdn && cd imgcdn && git init && npm init -y && npm pkg set type=module && npm install @aws-sdk/client-s3@3 sharp@0.35.3 mkdir -p data && cp .env.example .env ``` 2. Store under sha256 of the bytes; refuse non-images and files over the cap ### Done when - [ ] Two uploads of the same file store once - [ ] A renamed executable is refused - [ ] The stored file has no GPS EXIF ## M2 · Transforms imgproxy with the bucket as source, signed URLs, auto WebP and AVIF. ### Steps 1. docker-compose.yml for imgproxy with S3 source and signing enabled Files: `docker-compose.yml` 2. A helper img(key, width, height, format) building signed URLs ### Done when - [ ] One original serves at three widths in the browser's preferred format - [ ] An unsigned URL is refused ## M3 · Cache Long cache headers, a cache in front, purge by key. ### Steps 1. Cache-Control on transforms; Cloudflare or Caddy cache 2. A purge command by key ### Done when - [ ] The second request is served by the cache - [ ] Purge makes the next request miss ## M4 · Responsive helper srcset and sizes for every image in your site. ### Steps 1. A function producing srcset and sizes 2. A build step replacing plain img tags in your site ### Done when - [ ] A phone loads the smallest sufficient image; a desktop the largest ## M5 · Operate Lifecycle rules, memory limits, healthz, backups of the key index. ### Steps 1. imgproxy memory limits and /healthz; bucket lifecycle rules 2. README with the signing scheme and where originals live Files: `README.md` ### Done when - [ ] imgproxy survives a 50 MB hostile image - [ ] The README reaches a served image ## M6 · Serve several sites (production only) Per-site prefixes, tokens and metrics. ### Steps 1. Per-site key prefixes and upload tokens 2. Bandwidth and transform counts per site from the cache logs ### Done when - [ ] Two sites upload to separate prefixes - [ ] Per-site usage is visible
# Operations · Cloudinary ## Backup Originals are the asset: enable bucket versioning or replicate to a second bucket. ## Restore Point imgproxy at the copy. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on img domain and imgproxy memory. ## Incident checklist Rotate IMGPROXY_KEY; all old URLs stop, so regenerate. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Signing verified - [ ] Hostile image test passes - [ ] Bucket versioning or replication on ## Launch constraint Do not market omitted Cloudinary capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. From the bucket provider. S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com # Required. Bucket name. S3_BUCKET=images # Required · secret. Bucket access key. S3_ACCESS_KEY_ID=... # Required · secret. Bucket secret. S3_SECRET_ACCESS_KEY=... # Required · secret. openssl rand -hex 32. IMGPROXY_KEY=hex # Required · secret. openssl rand -hex 32. IMGPROXY_SALT=hex # Required · secret. Bearer token for the upload endpoint. UPLOAD_TOKEN=base64 # Required. Public base of the image domain. IMG_URL=https://img.yourdomain.com
$ choose a build depth, inspect the files, then open the complete pack in your agent
Images are solved; video is not. Teams with video, or with designers who need a library, pay.
xvideo transcoding and adaptive streaming
xthe media library and AI tagging
xglobal edge delivery you do not operate
xupload widgets and SDKs for every platform
Cloudinary pricing
plus$99/mo · monthly flat, credit-based · $1,188/yr
free tierThe free plan includes 25 monthly credits covering transformations, storage and bandwidth.
verified 2026-09-04 · source ↗
Is Cloudinary free?
The free plan includes 25 monthly credits covering transformations, storage and bandwidth. Paid is Plus at $99/mo (checked 2026-09-04).
Vibecode Cloudinary
Kinda. The core of Cloudinary is buildable in a weekend with the prompt on this page, but there are real gaps: video transcoding and adaptive streaming, the media library and AI tagging. Read the honest list above before committing.
How much does Cloudinary cost?
Cloudinary costs about $99/month (Plus, checked 2026-09-04), which is $1188 per year.
What do I lose by replacing Cloudinary?
Honestly: video transcoding and adaptive streaming; the media library and AI tagging; global edge delivery you do not operate; upload widgets and SDKs for every platform. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Cloudinary?
Yes: imgproxy (fast and secure image processing server), sharp (high-performance Node image processing). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.