Vibecode Bannerbear
track this build5 phases, 10 steps, beginner friendly0%Rendering a template with text into a PNG is what satori and resvg do at build time on this very site, at $0 per image. An HTTP endpoint around that is a sitting. Videos, the template editor and the integrations are what the fee buys.
You are building a lean indie version of Bannerbear. 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 ===== # Bannerbear · indie build An image generation API you host: templates as code, satori and resvg render them to PNG on demand behind signed URLs with a cache, in the same way this site renders 1,100 OG images for free. Estimated effort: **one sitting**. 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 | | --- | --- | --- | | Rendering | satori to SVG, @resvg/resvg-js to PNG, sharp to resize | no headless browser | | Runtime | Node 22, node:http | one endpoint | ## 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 - [ ] **Font files (TTF or OTF) for your templates** · free - Why: satori needs font files, not CSS font names. - Get it: Download from Google Fonts or Fontsource; commit them to the repo. - [ ] **An HMAC signing key** · free - Why: So nobody renders arbitrary text on your domain. - Get it: openssl rand -hex 32 into .env as SIGN_KEY. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. - 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: A public address you own, so links you share never break when a provider changes. - 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 ## Quick start ```sh mkdir imggen && cd imggen && git init && npm init -y && npm pkg set type=module && npm install satori@0.29.0 @resvg/resvg-js@2.6.2 sharp@0.35.3 mkdir -p templates renders fonts && 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 visual editor, the integrations. - the visual template editor - video and GIF rendering - Zapier, Make and Airtable integrations - CDN hosting of the results If one of those is essential to you, that is the reason to keep paying for Bannerbear, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Bannerbear 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 generation API like Bannerbear. 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. ### Stack (fixed, do not substitute) - Node 22 with node:http. satori for layout to SVG, @resvg/resvg-js for SVG to PNG, sharp for resizing. Fonts as files in the repo. No headless browser. ### Data model (create this before Phase 1) - templates/<name>.mjs exporting a function from params to a satori element tree, plus width, height and the fonts it needs. - renders/: cached PNGs named by a hash of template name and params. ### Phase 1 · Render one template Build: a render(template, params) function producing a PNG buffer, and a CLI that writes it to disk. One template: title, subtitle, a logo, a background. Done when: the CLI produces a crisp 1200x630 PNG with correct text wrapping and the right font. Do not build yet: HTTP, caching. ### Phase 2 · The endpoint Build: GET /img/:template?params renders on demand with a hard timeout, validates params against the template's declared schema, and caches by hash. Done when: the first request renders, the second is served from cache in under 10 ms, and an unknown param is refused. ### Phase 3 · Signed URLs Build: an HMAC signature over template and params so nobody can render arbitrary text on your domain; a helper that produces signed URLs. Done when: an unsigned or tampered URL is refused and a signed one renders. ### Phase 4 · More templates and formats Build: three more templates (a quote card, a product card, a social header), JPEG and WebP output, and a size parameter. Done when: each template renders correctly at two sizes and in each format. ### Phase 5 · Operate Build: cache eviction by age and size, a /healthz endpoint, a systemd unit, the README with a template-authoring guide. Done when: the cache stays under its cap and a new template goes from file to rendered URL by following the README. ### Out of scope (and why) - Video, the visual editor, the integrations. That is the subscription. ### README must contain - The signing scheme and why it exists. - How to add a font and a template. ===== AGENTS.md ===== # Agent instructions · Bannerbear indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: satori to SVG, @resvg/resvg-js to PNG, sharp to resize, Node 22, node:http. 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 · Bannerbear An image generation API you host: templates as code, satori and resvg render them to PNG on demand behind signed URLs with a cache, in the same way this site renders 1,100 OG images for free. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Render one template A crisp 1200x630 PNG from a satori tree. ### Steps 1. Install satori, resvg and sharp; write templates/card.mjs exporting a tree, size and fonts ```sh mkdir imggen && cd imggen && git init && npm init -y && npm pkg set type=module && npm install satori@0.29.0 @resvg/resvg-js@2.6.2 sharp@0.35.3 mkdir -p templates renders fonts && cp .env.example .env ``` 2. A CLI that renders to disk ### Done when - [ ] A 1200x630 PNG with correct wrapping and font ## Phase 2 · The endpoint GET /img/:template with validated params, timeout and cache. ### Steps 1. Validate params against the template's schema; render with a timeout 2. Cache by hash of template and params ### Done when - [ ] Second request served from cache under 10 ms - [ ] An unknown param is refused ## Phase 3 · Signed URLs Only URLs you generated render. ### Steps 1. HMAC over template and params; a helper that builds signed URLs 2. Refuse unsigned or tampered URLs ### Done when - [ ] A tampered URL is refused - [ ] A signed one renders ## Phase 4 · More templates and formats Three more templates, JPEG and WebP, a size parameter. ### Steps 1. Quote card, product card, social header 2. Format and size parameters via sharp ### Done when - [ ] Each template renders at two sizes in each format ## Phase 5 · Operate Eviction, healthz, service, authoring guide. ### Steps 1. Evict by age and CACHE_MAX_MB; /healthz; systemd; Caddy 2. README with the signing scheme and how to add a font and a template Files: `README.md` ### Done when - [ ] Cache stays under its cap - [ ] A new template is live by following the README ## Not in this build - Video, the visual editor, the integrations. ## After v1, if you want it - A preview page per template with a form - Batch rendering from CSV ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required · secret. openssl rand -hex 32. SIGN_KEY=hex # Required. Where PNGs are cached. CACHE_DIR=./renders # Optional. Cache size cap. CACHE_MAX_MB=500
You are building a lean indie version of Bannerbear. 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 ===== # Bannerbear · indie build An image generation API you host: templates as code, satori and resvg render them to PNG on demand behind signed URLs with a cache, in the same way this site renders 1,100 OG images for free. Estimated effort: **one sitting**. 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 | | --- | --- | --- | | Rendering | satori to SVG, @resvg/resvg-js to PNG, sharp to resize | no headless browser | | Runtime | Node 22, node:http | one endpoint | ## 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 - [ ] **Font files (TTF or OTF) for your templates** · free - Why: satori needs font files, not CSS font names. - Get it: Download from Google Fonts or Fontsource; commit them to the repo. - [ ] **An HMAC signing key** · free - Why: So nobody renders arbitrary text on your domain. - Get it: openssl rand -hex 32 into .env as SIGN_KEY. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. - 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: A public address you own, so links you share never break when a provider changes. - 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 ## Quick start ```sh mkdir imggen && cd imggen && git init && npm init -y && npm pkg set type=module && npm install satori@0.29.0 @resvg/resvg-js@2.6.2 sharp@0.35.3 mkdir -p templates renders fonts && 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 visual editor, the integrations. - the visual template editor - video and GIF rendering - Zapier, Make and Airtable integrations - CDN hosting of the results If one of those is essential to you, that is the reason to keep paying for Bannerbear, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Bannerbear 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 generation API like Bannerbear. 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. ### Stack (fixed, do not substitute) - Node 22 with node:http. satori for layout to SVG, @resvg/resvg-js for SVG to PNG, sharp for resizing. Fonts as files in the repo. No headless browser. ### Data model (create this before Phase 1) - templates/<name>.mjs exporting a function from params to a satori element tree, plus width, height and the fonts it needs. - renders/: cached PNGs named by a hash of template name and params. ### Phase 1 · Render one template Build: a render(template, params) function producing a PNG buffer, and a CLI that writes it to disk. One template: title, subtitle, a logo, a background. Done when: the CLI produces a crisp 1200x630 PNG with correct text wrapping and the right font. Do not build yet: HTTP, caching. ### Phase 2 · The endpoint Build: GET /img/:template?params renders on demand with a hard timeout, validates params against the template's declared schema, and caches by hash. Done when: the first request renders, the second is served from cache in under 10 ms, and an unknown param is refused. ### Phase 3 · Signed URLs Build: an HMAC signature over template and params so nobody can render arbitrary text on your domain; a helper that produces signed URLs. Done when: an unsigned or tampered URL is refused and a signed one renders. ### Phase 4 · More templates and formats Build: three more templates (a quote card, a product card, a social header), JPEG and WebP output, and a size parameter. Done when: each template renders correctly at two sizes and in each format. ### Phase 5 · Operate Build: cache eviction by age and size, a /healthz endpoint, a systemd unit, the README with a template-authoring guide. Done when: the cache stays under its cap and a new template goes from file to rendered URL by following the README. ### Out of scope (and why) - Video, the visual editor, the integrations. That is the subscription. ### README must contain - The signing scheme and why it exists. - How to add a font and a template. ===== AGENTS.md ===== # Agent instructions · Bannerbear indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: satori to SVG, @resvg/resvg-js to PNG, sharp to resize, Node 22, node:http. 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 · Bannerbear An image generation API you host: templates as code, satori and resvg render them to PNG on demand behind signed URLs with a cache, in the same way this site renders 1,100 OG images for free. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Render one template A crisp 1200x630 PNG from a satori tree. ### Steps 1. Install satori, resvg and sharp; write templates/card.mjs exporting a tree, size and fonts ```sh mkdir imggen && cd imggen && git init && npm init -y && npm pkg set type=module && npm install satori@0.29.0 @resvg/resvg-js@2.6.2 sharp@0.35.3 mkdir -p templates renders fonts && cp .env.example .env ``` 2. A CLI that renders to disk ### Done when - [ ] A 1200x630 PNG with correct wrapping and font ## Phase 2 · The endpoint GET /img/:template with validated params, timeout and cache. ### Steps 1. Validate params against the template's schema; render with a timeout 2. Cache by hash of template and params ### Done when - [ ] Second request served from cache under 10 ms - [ ] An unknown param is refused ## Phase 3 · Signed URLs Only URLs you generated render. ### Steps 1. HMAC over template and params; a helper that builds signed URLs 2. Refuse unsigned or tampered URLs ### Done when - [ ] A tampered URL is refused - [ ] A signed one renders ## Phase 4 · More templates and formats Three more templates, JPEG and WebP, a size parameter. ### Steps 1. Quote card, product card, social header 2. Format and size parameters via sharp ### Done when - [ ] Each template renders at two sizes in each format ## Phase 5 · Operate Eviction, healthz, service, authoring guide. ### Steps 1. Evict by age and CACHE_MAX_MB; /healthz; systemd; Caddy 2. README with the signing scheme and how to add a font and a template Files: `README.md` ### Done when - [ ] Cache stays under its cap - [ ] A new template is live by following the README ## Not in this build - Video, the visual editor, the integrations. ## After v1, if you want it - A preview page per template with a form - Batch rendering from CSV ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required · secret. openssl rand -hex 32. SIGN_KEY=hex # Required. Where PNGs are cached. CACHE_DIR=./renders # Optional. Cache size cap. CACHE_MAX_MB=500
You are building a production product version of Bannerbear. 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 ===== # Bannerbear · product brief ## Problem Rendering a template with text into a PNG is what satori and resvg do at build time on this very site, at $0 per image. An HTTP endpoint around that is a sitting. Videos, the template editor and the integrations are what the fee buys. ## Product outcome Image generation for your own products at zero per-image cost, with the editor and video honestly left to the vendor. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node 22 - the fonts you want to render, as files ## Explicit non-goals for v1 - Video, the visual editor, the integrations. - the visual template editor - video and GIF rendering - Zapier, Make and Airtable integrations - CDN hosting of the results ## Success criteria - Signing verified - Cache cap verified ===== BRIEF.md ===== # Build brief · Bannerbear 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 generation API like Bannerbear. 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. ### Stack (fixed, do not substitute) - Node 22 with node:http. satori for layout to SVG, @resvg/resvg-js for SVG to PNG, sharp for resizing. Fonts as files in the repo. No headless browser. ### Data model (create this before Phase 1) - templates/<name>.mjs exporting a function from params to a satori element tree, plus width, height and the fonts it needs. - renders/: cached PNGs named by a hash of template name and params. ### Phase 1 · Render one template Build: a render(template, params) function producing a PNG buffer, and a CLI that writes it to disk. One template: title, subtitle, a logo, a background. Done when: the CLI produces a crisp 1200x630 PNG with correct text wrapping and the right font. Do not build yet: HTTP, caching. ### Phase 2 · The endpoint Build: GET /img/:template?params renders on demand with a hard timeout, validates params against the template's declared schema, and caches by hash. Done when: the first request renders, the second is served from cache in under 10 ms, and an unknown param is refused. ### Phase 3 · Signed URLs Build: an HMAC signature over template and params so nobody can render arbitrary text on your domain; a helper that produces signed URLs. Done when: an unsigned or tampered URL is refused and a signed one renders. ### Phase 4 · More templates and formats Build: three more templates (a quote card, a product card, a social header), JPEG and WebP output, and a size parameter. Done when: each template renders correctly at two sizes and in each format. ### Phase 5 · Operate Build: cache eviction by age and size, a /healthz endpoint, a systemd unit, the README with a template-authoring guide. Done when: the cache stays under its cap and a new template goes from file to rendered URL by following the README. ### Out of scope (and why) - Video, the visual editor, the integrations. That is the subscription. ### README must contain - The signing scheme and why it exists. - How to add a font and a template. ===== ARCHITECTURE.md ===== # Architecture · Bannerbear ## Stack | Part | Choice | Why | | --- | --- | --- | | Rendering | satori to SVG, @resvg/resvg-js to PNG, sharp to resize | no headless browser | | Runtime | Node 22, node:http | one endpoint | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Templates | code modules | A JSON template format later | | Renderer | satori, resvg, sharp | A browser renderer for HTML fidelity | | Edge | signing and cache | A CDN in front | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PORT` · required · Any free port. - `SIGN_KEY` · required, secret · openssl rand -hex 32. - `CACHE_DIR` · required · Where PNGs are cached. - `CACHE_MAX_MB` · optional · Cache size cap. ## 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 · Bannerbear product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: satori to SVG, @resvg/resvg-js to PNG, sharp to resize, Node 22, node:http. - 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 · Bannerbear Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Render one template A crisp 1200x630 PNG from a satori tree. ### Steps 1. Install satori, resvg and sharp; write templates/card.mjs exporting a tree, size and fonts ```sh mkdir imggen && cd imggen && git init && npm init -y && npm pkg set type=module && npm install satori@0.29.0 @resvg/resvg-js@2.6.2 sharp@0.35.3 mkdir -p templates renders fonts && cp .env.example .env ``` 2. A CLI that renders to disk ### Done when - [ ] A 1200x630 PNG with correct wrapping and font ## M2 · The endpoint GET /img/:template with validated params, timeout and cache. ### Steps 1. Validate params against the template's schema; render with a timeout 2. Cache by hash of template and params ### Done when - [ ] Second request served from cache under 10 ms - [ ] An unknown param is refused ## M3 · Signed URLs Only URLs you generated render. ### Steps 1. HMAC over template and params; a helper that builds signed URLs 2. Refuse unsigned or tampered URLs ### Done when - [ ] A tampered URL is refused - [ ] A signed one renders ## M4 · More templates and formats Three more templates, JPEG and WebP, a size parameter. ### Steps 1. Quote card, product card, social header 2. Format and size parameters via sharp ### Done when - [ ] Each template renders at two sizes in each format ## M5 · Operate Eviction, healthz, service, authoring guide. ### Steps 1. Evict by age and CACHE_MAX_MB; /healthz; systemd; Caddy 2. README with the signing scheme and how to add a font and a template Files: `README.md` ### Done when - [ ] Cache stays under its cap - [ ] A new template is live by following the README ## M6 · Operate it like a product (production only) Only for the product-builder path: know when the renderer is down, never lose the database, and keep the server patched. ### Steps 1. Add a /healthz endpoint and an external uptime check against it Answer 200 with the build id and a quick database read. Point a free uptime monitor (or your own, from the Healthchecks entry on this site) at it so an outage is noticed before a user notices. 2. Write structured request logs and rotate them One JSON line per request: method, path, status, duration, no raw IPs. Rotate weekly with logrotate, keep eight. 3. Back the SQLite file up off the machine nightly and test a restore SQLite's .backup command makes a consistent copy while the app runs. Copy it to object storage or a second machine; then, once, restore it into a fresh checkout and confirm the app reads it. ```sh sqlite3 data/app.db ".backup '/tmp/app-$(date +%F).db'" rclone copy /tmp/app-$(date +%F).db remote:backups/ ``` 4. Lock the box down Firewall allowing only 22, 80 and 443; unattended security updates on; the app running as an unprivileged user under systemd with Restart=on-failure. ### Done when - [ ] Stopping the service triggers an uptime alert within a few minutes - [ ] A restore from last night's backup contains yesterday's data - [ ] A port scan from another machine shows only 22, 80 and 443 ===== OPERATIONS.md ===== # Operations · Bannerbear ## Backup Templates in git; cache is disposable. ## Restore Redeploy. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime and render latency. ## Incident checklist A leaked SIGN_KEY: rotate; old URLs stop working. 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 - [ ] Cache cap verified ## Launch constraint Do not market omitted Bannerbear 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. Any free port. PORT=3000 # Required · secret. openssl rand -hex 32. SIGN_KEY=hex # Required. Where PNGs are cached. CACHE_DIR=./renders # Optional. Cache size cap. CACHE_MAX_MB=500
# Bannerbear · indie build An image generation API you host: templates as code, satori and resvg render them to PNG on demand behind signed URLs with a cache, in the same way this site renders 1,100 OG images for free. Estimated effort: **one sitting**. 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 | | --- | --- | --- | | Rendering | satori to SVG, @resvg/resvg-js to PNG, sharp to resize | no headless browser | | Runtime | Node 22, node:http | one endpoint | ## 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 - [ ] **Font files (TTF or OTF) for your templates** · free - Why: satori needs font files, not CSS font names. - Get it: Download from Google Fonts or Fontsource; commit them to the repo. - [ ] **An HMAC signing key** · free - Why: So nobody renders arbitrary text on your domain. - Get it: openssl rand -hex 32 into .env as SIGN_KEY. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. - 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: A public address you own, so links you share never break when a provider changes. - 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 ## Quick start ```sh mkdir imggen && cd imggen && git init && npm init -y && npm pkg set type=module && npm install satori@0.29.0 @resvg/resvg-js@2.6.2 sharp@0.35.3 mkdir -p templates renders fonts && 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 visual editor, the integrations. - the visual template editor - video and GIF rendering - Zapier, Make and Airtable integrations - CDN hosting of the results If one of those is essential to you, that is the reason to keep paying for Bannerbear, and the README should say so rather than pretend.
# Build brief · Bannerbear 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 generation API like Bannerbear. 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. ### Stack (fixed, do not substitute) - Node 22 with node:http. satori for layout to SVG, @resvg/resvg-js for SVG to PNG, sharp for resizing. Fonts as files in the repo. No headless browser. ### Data model (create this before Phase 1) - templates/<name>.mjs exporting a function from params to a satori element tree, plus width, height and the fonts it needs. - renders/: cached PNGs named by a hash of template name and params. ### Phase 1 · Render one template Build: a render(template, params) function producing a PNG buffer, and a CLI that writes it to disk. One template: title, subtitle, a logo, a background. Done when: the CLI produces a crisp 1200x630 PNG with correct text wrapping and the right font. Do not build yet: HTTP, caching. ### Phase 2 · The endpoint Build: GET /img/:template?params renders on demand with a hard timeout, validates params against the template's declared schema, and caches by hash. Done when: the first request renders, the second is served from cache in under 10 ms, and an unknown param is refused. ### Phase 3 · Signed URLs Build: an HMAC signature over template and params so nobody can render arbitrary text on your domain; a helper that produces signed URLs. Done when: an unsigned or tampered URL is refused and a signed one renders. ### Phase 4 · More templates and formats Build: three more templates (a quote card, a product card, a social header), JPEG and WebP output, and a size parameter. Done when: each template renders correctly at two sizes and in each format. ### Phase 5 · Operate Build: cache eviction by age and size, a /healthz endpoint, a systemd unit, the README with a template-authoring guide. Done when: the cache stays under its cap and a new template goes from file to rendered URL by following the README. ### Out of scope (and why) - Video, the visual editor, the integrations. That is the subscription. ### README must contain - The signing scheme and why it exists. - How to add a font and a template.
# Agent instructions · Bannerbear indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: satori to SVG, @resvg/resvg-js to PNG, sharp to resize, Node 22, node:http. 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 · Bannerbear An image generation API you host: templates as code, satori and resvg render them to PNG on demand behind signed URLs with a cache, in the same way this site renders 1,100 OG images for free. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Render one template A crisp 1200x630 PNG from a satori tree. ### Steps 1. Install satori, resvg and sharp; write templates/card.mjs exporting a tree, size and fonts ```sh mkdir imggen && cd imggen && git init && npm init -y && npm pkg set type=module && npm install satori@0.29.0 @resvg/resvg-js@2.6.2 sharp@0.35.3 mkdir -p templates renders fonts && cp .env.example .env ``` 2. A CLI that renders to disk ### Done when - [ ] A 1200x630 PNG with correct wrapping and font ## Phase 2 · The endpoint GET /img/:template with validated params, timeout and cache. ### Steps 1. Validate params against the template's schema; render with a timeout 2. Cache by hash of template and params ### Done when - [ ] Second request served from cache under 10 ms - [ ] An unknown param is refused ## Phase 3 · Signed URLs Only URLs you generated render. ### Steps 1. HMAC over template and params; a helper that builds signed URLs 2. Refuse unsigned or tampered URLs ### Done when - [ ] A tampered URL is refused - [ ] A signed one renders ## Phase 4 · More templates and formats Three more templates, JPEG and WebP, a size parameter. ### Steps 1. Quote card, product card, social header 2. Format and size parameters via sharp ### Done when - [ ] Each template renders at two sizes in each format ## Phase 5 · Operate Eviction, healthz, service, authoring guide. ### Steps 1. Evict by age and CACHE_MAX_MB; /healthz; systemd; Caddy 2. README with the signing scheme and how to add a font and a template Files: `README.md` ### Done when - [ ] Cache stays under its cap - [ ] A new template is live by following the README ## Not in this build - Video, the visual editor, the integrations. ## After v1, if you want it - A preview page per template with a form - Batch rendering from CSV
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required · secret. openssl rand -hex 32. SIGN_KEY=hex # Required. Where PNGs are cached. CACHE_DIR=./renders # Optional. Cache size cap. CACHE_MAX_MB=500
# Bannerbear · product brief ## Problem Rendering a template with text into a PNG is what satori and resvg do at build time on this very site, at $0 per image. An HTTP endpoint around that is a sitting. Videos, the template editor and the integrations are what the fee buys. ## Product outcome Image generation for your own products at zero per-image cost, with the editor and video honestly left to the vendor. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node 22 - the fonts you want to render, as files ## Explicit non-goals for v1 - Video, the visual editor, the integrations. - the visual template editor - video and GIF rendering - Zapier, Make and Airtable integrations - CDN hosting of the results ## Success criteria - Signing verified - Cache cap verified
# Build brief · Bannerbear 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 generation API like Bannerbear. 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. ### Stack (fixed, do not substitute) - Node 22 with node:http. satori for layout to SVG, @resvg/resvg-js for SVG to PNG, sharp for resizing. Fonts as files in the repo. No headless browser. ### Data model (create this before Phase 1) - templates/<name>.mjs exporting a function from params to a satori element tree, plus width, height and the fonts it needs. - renders/: cached PNGs named by a hash of template name and params. ### Phase 1 · Render one template Build: a render(template, params) function producing a PNG buffer, and a CLI that writes it to disk. One template: title, subtitle, a logo, a background. Done when: the CLI produces a crisp 1200x630 PNG with correct text wrapping and the right font. Do not build yet: HTTP, caching. ### Phase 2 · The endpoint Build: GET /img/:template?params renders on demand with a hard timeout, validates params against the template's declared schema, and caches by hash. Done when: the first request renders, the second is served from cache in under 10 ms, and an unknown param is refused. ### Phase 3 · Signed URLs Build: an HMAC signature over template and params so nobody can render arbitrary text on your domain; a helper that produces signed URLs. Done when: an unsigned or tampered URL is refused and a signed one renders. ### Phase 4 · More templates and formats Build: three more templates (a quote card, a product card, a social header), JPEG and WebP output, and a size parameter. Done when: each template renders correctly at two sizes and in each format. ### Phase 5 · Operate Build: cache eviction by age and size, a /healthz endpoint, a systemd unit, the README with a template-authoring guide. Done when: the cache stays under its cap and a new template goes from file to rendered URL by following the README. ### Out of scope (and why) - Video, the visual editor, the integrations. That is the subscription. ### README must contain - The signing scheme and why it exists. - How to add a font and a template.
# Architecture · Bannerbear ## Stack | Part | Choice | Why | | --- | --- | --- | | Rendering | satori to SVG, @resvg/resvg-js to PNG, sharp to resize | no headless browser | | Runtime | Node 22, node:http | one endpoint | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Templates | code modules | A JSON template format later | | Renderer | satori, resvg, sharp | A browser renderer for HTML fidelity | | Edge | signing and cache | A CDN in front | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PORT` · required · Any free port. - `SIGN_KEY` · required, secret · openssl rand -hex 32. - `CACHE_DIR` · required · Where PNGs are cached. - `CACHE_MAX_MB` · optional · Cache size cap. ## 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 · Bannerbear product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: satori to SVG, @resvg/resvg-js to PNG, sharp to resize, Node 22, node:http. - 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 · Bannerbear Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Render one template A crisp 1200x630 PNG from a satori tree. ### Steps 1. Install satori, resvg and sharp; write templates/card.mjs exporting a tree, size and fonts ```sh mkdir imggen && cd imggen && git init && npm init -y && npm pkg set type=module && npm install satori@0.29.0 @resvg/resvg-js@2.6.2 sharp@0.35.3 mkdir -p templates renders fonts && cp .env.example .env ``` 2. A CLI that renders to disk ### Done when - [ ] A 1200x630 PNG with correct wrapping and font ## M2 · The endpoint GET /img/:template with validated params, timeout and cache. ### Steps 1. Validate params against the template's schema; render with a timeout 2. Cache by hash of template and params ### Done when - [ ] Second request served from cache under 10 ms - [ ] An unknown param is refused ## M3 · Signed URLs Only URLs you generated render. ### Steps 1. HMAC over template and params; a helper that builds signed URLs 2. Refuse unsigned or tampered URLs ### Done when - [ ] A tampered URL is refused - [ ] A signed one renders ## M4 · More templates and formats Three more templates, JPEG and WebP, a size parameter. ### Steps 1. Quote card, product card, social header 2. Format and size parameters via sharp ### Done when - [ ] Each template renders at two sizes in each format ## M5 · Operate Eviction, healthz, service, authoring guide. ### Steps 1. Evict by age and CACHE_MAX_MB; /healthz; systemd; Caddy 2. README with the signing scheme and how to add a font and a template Files: `README.md` ### Done when - [ ] Cache stays under its cap - [ ] A new template is live by following the README ## M6 · Operate it like a product (production only) Only for the product-builder path: know when the renderer is down, never lose the database, and keep the server patched. ### Steps 1. Add a /healthz endpoint and an external uptime check against it Answer 200 with the build id and a quick database read. Point a free uptime monitor (or your own, from the Healthchecks entry on this site) at it so an outage is noticed before a user notices. 2. Write structured request logs and rotate them One JSON line per request: method, path, status, duration, no raw IPs. Rotate weekly with logrotate, keep eight. 3. Back the SQLite file up off the machine nightly and test a restore SQLite's .backup command makes a consistent copy while the app runs. Copy it to object storage or a second machine; then, once, restore it into a fresh checkout and confirm the app reads it. ```sh sqlite3 data/app.db ".backup '/tmp/app-$(date +%F).db'" rclone copy /tmp/app-$(date +%F).db remote:backups/ ``` 4. Lock the box down Firewall allowing only 22, 80 and 443; unattended security updates on; the app running as an unprivileged user under systemd with Restart=on-failure. ### Done when - [ ] Stopping the service triggers an uptime alert within a few minutes - [ ] A restore from last night's backup contains yesterday's data - [ ] A port scan from another machine shows only 22, 80 and 443
# Operations · Bannerbear ## Backup Templates in git; cache is disposable. ## Restore Redeploy. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime and render latency. ## Incident checklist A leaked SIGN_KEY: rotate; old URLs stop working. 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 - [ ] Cache cap verified ## Launch constraint Do not market omitted Bannerbear 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. Any free port. PORT=3000 # Required · secret. openssl rand -hex 32. SIGN_KEY=hex # Required. Where PNGs are cached. CACHE_DIR=./renders # Optional. Cache size cap. CACHE_MAX_MB=500
$ choose a build depth, inspect the files, then open the complete pack in your agent
Marketers pay to design the template in a browser and wire it to a spreadsheet without an engineer.
xthe visual template editor
xvideo and GIF rendering
xZapier, Make and Airtable integrations
xCDN hosting of the results
Vibecode Bannerbear
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Bannerbear replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Bannerbear cost?
Bannerbear costs about $49/month (Automate, checked 2026-09-04), which is $588 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Bannerbear?
Honestly: the visual template editor; video and GIF rendering; Zapier, Make and Airtable integrations; CDN hosting of the results. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Bannerbear?
Yes: satori (HTML and CSS to SVG, the engine behind OG image generation). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.