Vibecode Netlify
track this build4 phases, 12 steps, beginner friendly0%Static hosting and functions are easy to assemble, but Netlify's value is Git-based deploys, previews, forms, redirects, edge functions, and managed platform reliability.
You are building a lean indie version of Netlify. 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 ===== # Netlify · indie build A hosting platform whose defining property is that a deploy is atomic: a site is never half old and half new, every deploy stays addressable forever, and rolling back is repointing rather than rebuilding. Estimated effort: **multi-day**. 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 | | --- | --- | --- | | Storage | Content-addressed files with an immutable manifest per deploy | identical files across deploys cost one copy and rollback is a pointer move | | Serving | A manifest lookup per request resolving to content by hash | atomicity comes from serving one manifest rather than a mutable directory | | Builds | Isolated build containers with a cache and a hard timeout | builds run arbitrary code and must not be able to affect anything else | | Previews | A deploy per branch and per pull request, addressable separately | reviewing a change is the feature that justifies the whole model | | Runtime | TypeScript with PostgreSQL and object storage | manifests and deploy history are relational, files are not | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the control plane, the serving layer and the build orchestrator. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free locally - Why: Deploy history, manifests and domain state. - Get it: Docker or your package manager. - Verify: psql connects and creates a table - [ ] **A container runtime for build isolation** · free - Why: Builds execute arbitrary code from a repository and running them on the host is not something to do even once. - Get it: Docker or an equivalent, with a plan for network and filesystem restriction per build. - Verify: A build container cannot read the host filesystem or reach an unexpected network address - [ ] **Object storage for content-addressed files** · pennies per month at small scale - Why: Blobs are stored once per distinct hash and referenced by many deploys, which is what makes keeping every deploy affordable. - Get it: Any provider with cheap storage and a lifecycle policy for cleanup. - Verify: A file uploads by hash and is retrievable by that hash - [ ] **A domain you control** · the price of a domain - Why: Certificate issuance, apex handling and renewal cannot be tested without a real domain and real records. - Get it: Any domain where you can change records, ideally with a subdomain free for testing. - Verify: A record change propagates and a certificate issues successfully ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Edge functions. - A global network. - Forms, identity and platform conveniences. - Multi-tenant scale and abuse handling. - deploy previews - CDN/edge functions - forms - rollbacks - team workflows - uptime/support If one of those is essential to you, that is the reason to keep paying for Netlify, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Netlify 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 static site hosting with atomic deploys, previews per branch and instant rollback to replace Netlify. 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) - Storage: Content-addressed files with an immutable manifest per deploy · identical files across deploys cost one copy and rollback is a pointer move - Serving: A manifest lookup per request resolving to content by hash · atomicity comes from serving one manifest rather than a mutable directory - Builds: Isolated build containers with a cache and a hard timeout · builds run arbitrary code and must not be able to affect anything else - Previews: A deploy per branch and per pull request, addressable separately · reviewing a change is the feature that justifies the whole model - Runtime: TypeScript with PostgreSQL and object storage · manifests and deploy history are relational, files are not ### Data model (create this before Phase 1) - sites(id, name, production_deploy_id, custom_domains_json, build_config_json) - deploys(id, site_id, branch, commit_sha, state, manifest_id, built_at, build_log_path, context) · context is production, branch or preview - manifests(id, deploy_id, entries_json, entry_count, total_bytes) · path to content hash, immutable - blobs(content_hash, storage_key, bytes, first_seen_at, refcount) · one copy per distinct file - domains(id, site_id, hostname, certificate_id, verified_at, primary) - redirects(deploy_id, ordinal, from_pattern, to_target, status, conditions_json) · part of the deploy, not of the site - A redirect belongs to a deploy rather than to a site, because rolling back must roll back the routing rules too. ### Phase 1 · Deploys that are all or nothing Build: Upload files content addressed, build an immutable manifest, and switch the site to it in one operation. Serve by resolving the current manifest per request. Verify no request during a deploy sees a mixture. Trap: Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. Done when: Requests during a deploy return either entirely the old version or entirely the new one, verified under load during a switch. Identical files across deploys are stored once, verified by checking storage after two similar deploys. A manifest is immutable once written. A failed upload leaves the previous deploy serving unchanged. Do not build yet: No builds, previews, domains or rollback. ### Phase 2 · Rollback as a pointer move Build: Keep every deploy addressable permanently at its own address. Roll back by repointing the site at a previous manifest. Roll back redirects and headers with it, since they are part of the deploy. Trap: Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. Done when: Rolling back takes seconds and requires no rebuild, measured. Redirects and headers roll back with the files, verified by changing a redirect and reverting. Every past deploy remains addressable at its own permanent address. Rolling back and forward again produces byte-identical serving. Do not build yet: No builds, previews or domains yet. ### Phase 3 · Running someone else's build safely Build: Run builds in isolated containers with no access to other sites, the platform's own credentials or the network beyond declared dependencies. Enforce a hard timeout and a resource ceiling. Stream logs and keep them. Trap: A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. Done when: A build cannot read another site's files or the platform's credentials, tested with a deliberately hostile build script. Builds terminate at a hard timeout with the log preserved. Environment variables are scoped to the site and secrets are not printed in logs. A build cache speeds a repeat build without leaking between sites. Do not build yet: No previews or domains yet. ### Phase 4 · A preview per change Build: Deploy each branch and pull request to its own address, distinct from production. Prevent previews from being indexed. Clean up preview deploys on a policy without breaking the permanent deploy addresses. Trap: Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. Done when: Each branch and pull request deploys to its own address. Previews are not indexable, enforced at the platform level rather than by site configuration. Preview cleanup follows a policy without breaking the permanent per-deploy addresses. A pull request updates its preview on each push without creating a new address. Do not build yet: No domains, certificates or functions yet. ### Phase 5 · Domains, certificates and going live Build: Verify domain ownership, issue and renew certificates automatically, and handle the apex and subdomain cases. Alert well before expiry rather than at it. Handle a domain whose records were changed by someone else. Trap: Automatic certificate renewal that fails silently produces a site presenting an expired certificate, which browsers block outright, and the first you hear is a customer telling you the site is down. Alert on renewal failure well before expiry and treat a failed renewal as an incident rather than a retry. Done when: A custom domain verifies and receives a valid certificate. Renewal happens automatically and a failure alerts well before expiry rather than at it. Apex and subdomain configurations both work with their documented record requirements. A domain whose records changed is detected and reported rather than silently failing renewal. Do not build yet: Nothing after this. ### Out of scope (and why) - Serverless functions at the edge, which is a substantial runtime and isolation project of its own. - A global network. This runs from one place, and latency for distant visitors is the honest trade. - Form handling, identity and the other conveniences that make the hosted product a platform. - Multi-tenant scale and abuse handling, which is where a hosting business actually lives. ### README must contain - That a deploy is atomic because the manifest is built first and switched in one operation - That redirects and headers belong to the deploy so a rollback restores the routing too - That every deploy stays permanently addressable, which is what makes rollback a pointer move - That builds run arbitrary repository code and are isolated with no ambient platform credentials - That previews are non-indexable at the platform level rather than by site configuration ===== AGENTS.md ===== # Agent instructions · Netlify indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Content-addressed files with an immutable manifest per deploy, A manifest lookup per request resolving to content by hash, Isolated build containers with a cache and a hard timeout, A deploy per branch and per pull request, addressable separately, TypeScript with PostgreSQL and object storage. 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". ## Known traps - Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. - Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. - A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. - Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. ===== BUILD_PLAN.md ===== # Build plan · Netlify A hosting platform whose defining property is that a deploy is atomic: a site is never half old and half new, every deploy stays addressable forever, and rolling back is repointing rather than rebuilding. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Deploys that are all or nothing A site is never partly updated, even mid-deploy. ### Steps 1. Content address every file Identical files cost one copy and rollback becomes a pointer move. Files: `src/blobs.ts` 2. Build the manifest before switching Uploading into a live directory is the half-updated page. Files: `src/manifest.ts` 3. Switch atomically and test under load During the switch, not before and after it. Files: `tests/atomic.test.ts` ### Done when - [ ] No request during a deploy sees a mixture - [ ] Identical files are stored once - [ ] Manifests are immutable - [ ] A failed upload leaves the previous deploy serving ### Watch out - Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. ## Phase 2 · Rollback as a pointer move Reverting to any previous deploy takes seconds and needs no rebuild. ### Steps 1. Keep deploys permanently addressable Each at its own address, forever. It costs almost nothing. Files: `src/deploys.ts` 2. Attach routing to the deploy A rollback with the new redirects is a combination nobody tested. Files: `src/redirects.ts` 3. Make rollback a pointer move Seconds, not a rebuild. Measure it. Files: `src/rollback.ts` ### Done when - [ ] Rollback takes seconds without rebuilding - [ ] Redirects and headers roll back with the files - [ ] Past deploys stay permanently addressable - [ ] Rolling back and forward is byte-identical ### Watch out - Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. ## Phase 3 · Running someone else's build safely A build runs arbitrary code without reaching anything it should not. ### Steps 1. Isolate per build with no ambient credentials A build runs arbitrary code from a repository. Files: `src/build/sandbox.ts`, `tests/build-isolation.test.ts` 2. Enforce timeouts and ceilings A build that never finishes is a resource leak with a queue behind it. Files: `src/build/limits.ts` 3. Keep logs and redact secrets Logs are read by people debugging and are the usual place secrets escape. Files: `src/build/logs.ts` ### Done when - [ ] A hostile build cannot reach other sites or platform credentials - [ ] Builds terminate at a hard timeout with logs kept - [ ] Secrets do not appear in logs - [ ] The build cache does not leak between sites ### Watch out - A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. ## Phase 4 · A preview per change Every branch and pull request gets its own permanent address. ### Steps 1. Address previews separately from production Predictable per branch and per pull request. Files: `src/previews.ts` 2. Disable indexing at the platform Not per site. Someone will forget and it is found months later. Files: `src/serve.ts` 3. Clean up on a policy Without breaking permanent deploy addresses, which are the rollback path. Files: `src/cleanup.ts` ### Done when - [ ] Branches and pull requests get their own addresses - [ ] Previews are not indexable, enforced platform side - [ ] Cleanup does not break deploy addresses - [ ] A pull request reuses its preview address ### Watch out - Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. ## Not in this build - Edge functions. - A global network. - Forms, identity and platform conveniences. - Multi-tenant scale and abuse handling. ## After v1, if you want it - Deploy notifications with the preview address, which is where the review workflow starts - Split testing between two deploys, which the manifest model makes straightforward - Build plugins with the same isolation guarantees, once the sandbox is proven ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Sites, deploys and domain state. DATABASE_URL=postgres://localhost:5432/hosting # Required. Content-addressed files, shared across deploys. BLOB_BUCKET=s3://deploy-blobs # Required. Hard. A build that never finishes blocks the queue behind it. BUILD_TIMEOUT_SECONDS=900 # Required. Present as a constant. Indexed previews are found months later in search results. PREVIEW_INDEXABLE=never # Required. Permanent deploy addresses are what make rollback a pointer move. DEPLOY_RETENTION=forever # Required. Early, with alerting on failure. An expired certificate blocks the site entirely. CERT_RENEW_DAYS_BEFORE=30
You are building a lean indie version of Netlify. 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 ===== # Netlify · indie build A hosting platform whose defining property is that a deploy is atomic: a site is never half old and half new, every deploy stays addressable forever, and rolling back is repointing rather than rebuilding. Estimated effort: **multi-day**. 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 | | --- | --- | --- | | Storage | Content-addressed files with an immutable manifest per deploy | identical files across deploys cost one copy and rollback is a pointer move | | Serving | A manifest lookup per request resolving to content by hash | atomicity comes from serving one manifest rather than a mutable directory | | Builds | Isolated build containers with a cache and a hard timeout | builds run arbitrary code and must not be able to affect anything else | | Previews | A deploy per branch and per pull request, addressable separately | reviewing a change is the feature that justifies the whole model | | Runtime | TypeScript with PostgreSQL and object storage | manifests and deploy history are relational, files are not | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the control plane, the serving layer and the build orchestrator. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free locally - Why: Deploy history, manifests and domain state. - Get it: Docker or your package manager. - Verify: psql connects and creates a table - [ ] **A container runtime for build isolation** · free - Why: Builds execute arbitrary code from a repository and running them on the host is not something to do even once. - Get it: Docker or an equivalent, with a plan for network and filesystem restriction per build. - Verify: A build container cannot read the host filesystem or reach an unexpected network address - [ ] **Object storage for content-addressed files** · pennies per month at small scale - Why: Blobs are stored once per distinct hash and referenced by many deploys, which is what makes keeping every deploy affordable. - Get it: Any provider with cheap storage and a lifecycle policy for cleanup. - Verify: A file uploads by hash and is retrievable by that hash - [ ] **A domain you control** · the price of a domain - Why: Certificate issuance, apex handling and renewal cannot be tested without a real domain and real records. - Get it: Any domain where you can change records, ideally with a subdomain free for testing. - Verify: A record change propagates and a certificate issues successfully ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Edge functions. - A global network. - Forms, identity and platform conveniences. - Multi-tenant scale and abuse handling. - deploy previews - CDN/edge functions - forms - rollbacks - team workflows - uptime/support If one of those is essential to you, that is the reason to keep paying for Netlify, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Netlify 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 static site hosting with atomic deploys, previews per branch and instant rollback to replace Netlify. 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) - Storage: Content-addressed files with an immutable manifest per deploy · identical files across deploys cost one copy and rollback is a pointer move - Serving: A manifest lookup per request resolving to content by hash · atomicity comes from serving one manifest rather than a mutable directory - Builds: Isolated build containers with a cache and a hard timeout · builds run arbitrary code and must not be able to affect anything else - Previews: A deploy per branch and per pull request, addressable separately · reviewing a change is the feature that justifies the whole model - Runtime: TypeScript with PostgreSQL and object storage · manifests and deploy history are relational, files are not ### Data model (create this before Phase 1) - sites(id, name, production_deploy_id, custom_domains_json, build_config_json) - deploys(id, site_id, branch, commit_sha, state, manifest_id, built_at, build_log_path, context) · context is production, branch or preview - manifests(id, deploy_id, entries_json, entry_count, total_bytes) · path to content hash, immutable - blobs(content_hash, storage_key, bytes, first_seen_at, refcount) · one copy per distinct file - domains(id, site_id, hostname, certificate_id, verified_at, primary) - redirects(deploy_id, ordinal, from_pattern, to_target, status, conditions_json) · part of the deploy, not of the site - A redirect belongs to a deploy rather than to a site, because rolling back must roll back the routing rules too. ### Phase 1 · Deploys that are all or nothing Build: Upload files content addressed, build an immutable manifest, and switch the site to it in one operation. Serve by resolving the current manifest per request. Verify no request during a deploy sees a mixture. Trap: Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. Done when: Requests during a deploy return either entirely the old version or entirely the new one, verified under load during a switch. Identical files across deploys are stored once, verified by checking storage after two similar deploys. A manifest is immutable once written. A failed upload leaves the previous deploy serving unchanged. Do not build yet: No builds, previews, domains or rollback. ### Phase 2 · Rollback as a pointer move Build: Keep every deploy addressable permanently at its own address. Roll back by repointing the site at a previous manifest. Roll back redirects and headers with it, since they are part of the deploy. Trap: Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. Done when: Rolling back takes seconds and requires no rebuild, measured. Redirects and headers roll back with the files, verified by changing a redirect and reverting. Every past deploy remains addressable at its own permanent address. Rolling back and forward again produces byte-identical serving. Do not build yet: No builds, previews or domains yet. ### Phase 3 · Running someone else's build safely Build: Run builds in isolated containers with no access to other sites, the platform's own credentials or the network beyond declared dependencies. Enforce a hard timeout and a resource ceiling. Stream logs and keep them. Trap: A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. Done when: A build cannot read another site's files or the platform's credentials, tested with a deliberately hostile build script. Builds terminate at a hard timeout with the log preserved. Environment variables are scoped to the site and secrets are not printed in logs. A build cache speeds a repeat build without leaking between sites. Do not build yet: No previews or domains yet. ### Phase 4 · A preview per change Build: Deploy each branch and pull request to its own address, distinct from production. Prevent previews from being indexed. Clean up preview deploys on a policy without breaking the permanent deploy addresses. Trap: Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. Done when: Each branch and pull request deploys to its own address. Previews are not indexable, enforced at the platform level rather than by site configuration. Preview cleanup follows a policy without breaking the permanent per-deploy addresses. A pull request updates its preview on each push without creating a new address. Do not build yet: No domains, certificates or functions yet. ### Phase 5 · Domains, certificates and going live Build: Verify domain ownership, issue and renew certificates automatically, and handle the apex and subdomain cases. Alert well before expiry rather than at it. Handle a domain whose records were changed by someone else. Trap: Automatic certificate renewal that fails silently produces a site presenting an expired certificate, which browsers block outright, and the first you hear is a customer telling you the site is down. Alert on renewal failure well before expiry and treat a failed renewal as an incident rather than a retry. Done when: A custom domain verifies and receives a valid certificate. Renewal happens automatically and a failure alerts well before expiry rather than at it. Apex and subdomain configurations both work with their documented record requirements. A domain whose records changed is detected and reported rather than silently failing renewal. Do not build yet: Nothing after this. ### Out of scope (and why) - Serverless functions at the edge, which is a substantial runtime and isolation project of its own. - A global network. This runs from one place, and latency for distant visitors is the honest trade. - Form handling, identity and the other conveniences that make the hosted product a platform. - Multi-tenant scale and abuse handling, which is where a hosting business actually lives. ### README must contain - That a deploy is atomic because the manifest is built first and switched in one operation - That redirects and headers belong to the deploy so a rollback restores the routing too - That every deploy stays permanently addressable, which is what makes rollback a pointer move - That builds run arbitrary repository code and are isolated with no ambient platform credentials - That previews are non-indexable at the platform level rather than by site configuration ===== AGENTS.md ===== # Agent instructions · Netlify indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Content-addressed files with an immutable manifest per deploy, A manifest lookup per request resolving to content by hash, Isolated build containers with a cache and a hard timeout, A deploy per branch and per pull request, addressable separately, TypeScript with PostgreSQL and object storage. 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". ## Known traps - Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. - Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. - A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. - Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. ===== BUILD_PLAN.md ===== # Build plan · Netlify A hosting platform whose defining property is that a deploy is atomic: a site is never half old and half new, every deploy stays addressable forever, and rolling back is repointing rather than rebuilding. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Deploys that are all or nothing A site is never partly updated, even mid-deploy. ### Steps 1. Content address every file Identical files cost one copy and rollback becomes a pointer move. Files: `src/blobs.ts` 2. Build the manifest before switching Uploading into a live directory is the half-updated page. Files: `src/manifest.ts` 3. Switch atomically and test under load During the switch, not before and after it. Files: `tests/atomic.test.ts` ### Done when - [ ] No request during a deploy sees a mixture - [ ] Identical files are stored once - [ ] Manifests are immutable - [ ] A failed upload leaves the previous deploy serving ### Watch out - Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. ## Phase 2 · Rollback as a pointer move Reverting to any previous deploy takes seconds and needs no rebuild. ### Steps 1. Keep deploys permanently addressable Each at its own address, forever. It costs almost nothing. Files: `src/deploys.ts` 2. Attach routing to the deploy A rollback with the new redirects is a combination nobody tested. Files: `src/redirects.ts` 3. Make rollback a pointer move Seconds, not a rebuild. Measure it. Files: `src/rollback.ts` ### Done when - [ ] Rollback takes seconds without rebuilding - [ ] Redirects and headers roll back with the files - [ ] Past deploys stay permanently addressable - [ ] Rolling back and forward is byte-identical ### Watch out - Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. ## Phase 3 · Running someone else's build safely A build runs arbitrary code without reaching anything it should not. ### Steps 1. Isolate per build with no ambient credentials A build runs arbitrary code from a repository. Files: `src/build/sandbox.ts`, `tests/build-isolation.test.ts` 2. Enforce timeouts and ceilings A build that never finishes is a resource leak with a queue behind it. Files: `src/build/limits.ts` 3. Keep logs and redact secrets Logs are read by people debugging and are the usual place secrets escape. Files: `src/build/logs.ts` ### Done when - [ ] A hostile build cannot reach other sites or platform credentials - [ ] Builds terminate at a hard timeout with logs kept - [ ] Secrets do not appear in logs - [ ] The build cache does not leak between sites ### Watch out - A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. ## Phase 4 · A preview per change Every branch and pull request gets its own permanent address. ### Steps 1. Address previews separately from production Predictable per branch and per pull request. Files: `src/previews.ts` 2. Disable indexing at the platform Not per site. Someone will forget and it is found months later. Files: `src/serve.ts` 3. Clean up on a policy Without breaking permanent deploy addresses, which are the rollback path. Files: `src/cleanup.ts` ### Done when - [ ] Branches and pull requests get their own addresses - [ ] Previews are not indexable, enforced platform side - [ ] Cleanup does not break deploy addresses - [ ] A pull request reuses its preview address ### Watch out - Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. ## Not in this build - Edge functions. - A global network. - Forms, identity and platform conveniences. - Multi-tenant scale and abuse handling. ## After v1, if you want it - Deploy notifications with the preview address, which is where the review workflow starts - Split testing between two deploys, which the manifest model makes straightforward - Build plugins with the same isolation guarantees, once the sandbox is proven ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Sites, deploys and domain state. DATABASE_URL=postgres://localhost:5432/hosting # Required. Content-addressed files, shared across deploys. BLOB_BUCKET=s3://deploy-blobs # Required. Hard. A build that never finishes blocks the queue behind it. BUILD_TIMEOUT_SECONDS=900 # Required. Present as a constant. Indexed previews are found months later in search results. PREVIEW_INDEXABLE=never # Required. Permanent deploy addresses are what make rollback a pointer move. DEPLOY_RETENTION=forever # Required. Early, with alerting on failure. An expired certificate blocks the site entirely. CERT_RENEW_DAYS_BEFORE=30
You are building a production product version of Netlify. 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 ===== # Netlify · product brief ## Problem Static hosting and functions are easy to assemble, but Netlify's value is Git-based deploys, previews, forms, redirects, edge functions, and managed platform reliability. ## Product outcome Hosting where a deploy is never half applied, any past version is one pointer move away, and a build running arbitrary repository code cannot reach anything but its own site. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - VPS or cloud static hosting - CI/CD - domain/DNS - SSL - serverless or container functions - monitoring ## Explicit non-goals for v1 - Edge functions. - A global network. - Forms, identity and platform conveniences. - Multi-tenant scale and abuse handling. - deploy previews - CDN/edge functions - forms - rollbacks - team workflows - uptime/support ## Success criteria - No request during a deploy is confirmed to see a mixture - Rollback is confirmed to restore redirects with the files - A hostile build is confirmed unable to reach other sites - Previews are confirmed non-indexable at the platform level - Certificate renewal failure is confirmed to alert before expiry ===== BRIEF.md ===== # Build brief · Netlify 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 static site hosting with atomic deploys, previews per branch and instant rollback to replace Netlify. 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) - Storage: Content-addressed files with an immutable manifest per deploy · identical files across deploys cost one copy and rollback is a pointer move - Serving: A manifest lookup per request resolving to content by hash · atomicity comes from serving one manifest rather than a mutable directory - Builds: Isolated build containers with a cache and a hard timeout · builds run arbitrary code and must not be able to affect anything else - Previews: A deploy per branch and per pull request, addressable separately · reviewing a change is the feature that justifies the whole model - Runtime: TypeScript with PostgreSQL and object storage · manifests and deploy history are relational, files are not ### Data model (create this before Phase 1) - sites(id, name, production_deploy_id, custom_domains_json, build_config_json) - deploys(id, site_id, branch, commit_sha, state, manifest_id, built_at, build_log_path, context) · context is production, branch or preview - manifests(id, deploy_id, entries_json, entry_count, total_bytes) · path to content hash, immutable - blobs(content_hash, storage_key, bytes, first_seen_at, refcount) · one copy per distinct file - domains(id, site_id, hostname, certificate_id, verified_at, primary) - redirects(deploy_id, ordinal, from_pattern, to_target, status, conditions_json) · part of the deploy, not of the site - A redirect belongs to a deploy rather than to a site, because rolling back must roll back the routing rules too. ### Phase 1 · Deploys that are all or nothing Build: Upload files content addressed, build an immutable manifest, and switch the site to it in one operation. Serve by resolving the current manifest per request. Verify no request during a deploy sees a mixture. Trap: Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. Done when: Requests during a deploy return either entirely the old version or entirely the new one, verified under load during a switch. Identical files across deploys are stored once, verified by checking storage after two similar deploys. A manifest is immutable once written. A failed upload leaves the previous deploy serving unchanged. Do not build yet: No builds, previews, domains or rollback. ### Phase 2 · Rollback as a pointer move Build: Keep every deploy addressable permanently at its own address. Roll back by repointing the site at a previous manifest. Roll back redirects and headers with it, since they are part of the deploy. Trap: Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. Done when: Rolling back takes seconds and requires no rebuild, measured. Redirects and headers roll back with the files, verified by changing a redirect and reverting. Every past deploy remains addressable at its own permanent address. Rolling back and forward again produces byte-identical serving. Do not build yet: No builds, previews or domains yet. ### Phase 3 · Running someone else's build safely Build: Run builds in isolated containers with no access to other sites, the platform's own credentials or the network beyond declared dependencies. Enforce a hard timeout and a resource ceiling. Stream logs and keep them. Trap: A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. Done when: A build cannot read another site's files or the platform's credentials, tested with a deliberately hostile build script. Builds terminate at a hard timeout with the log preserved. Environment variables are scoped to the site and secrets are not printed in logs. A build cache speeds a repeat build without leaking between sites. Do not build yet: No previews or domains yet. ### Phase 4 · A preview per change Build: Deploy each branch and pull request to its own address, distinct from production. Prevent previews from being indexed. Clean up preview deploys on a policy without breaking the permanent deploy addresses. Trap: Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. Done when: Each branch and pull request deploys to its own address. Previews are not indexable, enforced at the platform level rather than by site configuration. Preview cleanup follows a policy without breaking the permanent per-deploy addresses. A pull request updates its preview on each push without creating a new address. Do not build yet: No domains, certificates or functions yet. ### Phase 5 · Domains, certificates and going live Build: Verify domain ownership, issue and renew certificates automatically, and handle the apex and subdomain cases. Alert well before expiry rather than at it. Handle a domain whose records were changed by someone else. Trap: Automatic certificate renewal that fails silently produces a site presenting an expired certificate, which browsers block outright, and the first you hear is a customer telling you the site is down. Alert on renewal failure well before expiry and treat a failed renewal as an incident rather than a retry. Done when: A custom domain verifies and receives a valid certificate. Renewal happens automatically and a failure alerts well before expiry rather than at it. Apex and subdomain configurations both work with their documented record requirements. A domain whose records changed is detected and reported rather than silently failing renewal. Do not build yet: Nothing after this. ### Out of scope (and why) - Serverless functions at the edge, which is a substantial runtime and isolation project of its own. - A global network. This runs from one place, and latency for distant visitors is the honest trade. - Form handling, identity and the other conveniences that make the hosted product a platform. - Multi-tenant scale and abuse handling, which is where a hosting business actually lives. ### README must contain - That a deploy is atomic because the manifest is built first and switched in one operation - That redirects and headers belong to the deploy so a rollback restores the routing too - That every deploy stays permanently addressable, which is what makes rollback a pointer move - That builds run arbitrary repository code and are isolated with no ambient platform credentials - That previews are non-indexable at the platform level rather than by site configuration ===== ARCHITECTURE.md ===== # Architecture · Netlify ## Stack | Part | Choice | Why | | --- | --- | --- | | Storage | Content-addressed files with an immutable manifest per deploy | identical files across deploys cost one copy and rollback is a pointer move | | Serving | A manifest lookup per request resolving to content by hash | atomicity comes from serving one manifest rather than a mutable directory | | Builds | Isolated build containers with a cache and a hard timeout | builds run arbitrary code and must not be able to affect anything else | | Previews | A deploy per branch and per pull request, addressable separately | reviewing a change is the feature that justifies the whole model | | Runtime | TypeScript with PostgreSQL and object storage | manifests and deploy history are relational, files are not | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Blobs | Content-addressed file storage. | Any store, provided identity is the content hash. | | Manifests | Immutable per-deploy file maps. | Not swappable for a mutable directory, which is the half-updated page. | | Builds | Isolated execution with limits. | Any runtime, provided no ambient platform credentials are present. | | Previews | Per-branch addresses that are not indexable. | Any scheme, provided indexing is disabled at the platform. | | Domains | Verification, certificates and renewal alerting. | Any issuer, provided renewal failure alerts before expiry. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · Sites, deploys and domain state. - `BLOB_BUCKET` · required · Content-addressed files, shared across deploys. - `BUILD_TIMEOUT_SECONDS` · required · Hard. A build that never finishes blocks the queue behind it. - `PREVIEW_INDEXABLE` · required · Present as a constant. Indexed previews are found months later in search results. - `DEPLOY_RETENTION` · required · Permanent deploy addresses are what make rollback a pointer move. - `CERT_RENEW_DAYS_BEFORE` · required · Early, with alerting on failure. An expired certificate blocks the site entirely. ## 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 · Netlify product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Content-addressed files with an immutable manifest per deploy, A manifest lookup per request resolving to content by hash, Isolated build containers with a cache and a hard timeout, A deploy per branch and per pull request, addressable separately, TypeScript with PostgreSQL and object storage. - 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. ## Known traps - Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. - Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. - A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. - Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. - Automatic certificate renewal that fails silently produces a site presenting an expired certificate, which browsers block outright, and the first you hear is a customer telling you the site is down. Alert on renewal failure well before expiry and treat a failed renewal as an incident rather than a retry. ===== MILESTONES.md ===== # Delivery milestones · Netlify Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Deploys that are all or nothing A site is never partly updated, even mid-deploy. ### Steps 1. Content address every file Identical files cost one copy and rollback becomes a pointer move. Files: `src/blobs.ts` 2. Build the manifest before switching Uploading into a live directory is the half-updated page. Files: `src/manifest.ts` 3. Switch atomically and test under load During the switch, not before and after it. Files: `tests/atomic.test.ts` ### Done when - [ ] No request during a deploy sees a mixture - [ ] Identical files are stored once - [ ] Manifests are immutable - [ ] A failed upload leaves the previous deploy serving ### Watch out - Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. ## M2 · Rollback as a pointer move Reverting to any previous deploy takes seconds and needs no rebuild. ### Steps 1. Keep deploys permanently addressable Each at its own address, forever. It costs almost nothing. Files: `src/deploys.ts` 2. Attach routing to the deploy A rollback with the new redirects is a combination nobody tested. Files: `src/redirects.ts` 3. Make rollback a pointer move Seconds, not a rebuild. Measure it. Files: `src/rollback.ts` ### Done when - [ ] Rollback takes seconds without rebuilding - [ ] Redirects and headers roll back with the files - [ ] Past deploys stay permanently addressable - [ ] Rolling back and forward is byte-identical ### Watch out - Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. ## M3 · Running someone else's build safely A build runs arbitrary code without reaching anything it should not. ### Steps 1. Isolate per build with no ambient credentials A build runs arbitrary code from a repository. Files: `src/build/sandbox.ts`, `tests/build-isolation.test.ts` 2. Enforce timeouts and ceilings A build that never finishes is a resource leak with a queue behind it. Files: `src/build/limits.ts` 3. Keep logs and redact secrets Logs are read by people debugging and are the usual place secrets escape. Files: `src/build/logs.ts` ### Done when - [ ] A hostile build cannot reach other sites or platform credentials - [ ] Builds terminate at a hard timeout with logs kept - [ ] Secrets do not appear in logs - [ ] The build cache does not leak between sites ### Watch out - A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. ## M4 · A preview per change Every branch and pull request gets its own permanent address. ### Steps 1. Address previews separately from production Predictable per branch and per pull request. Files: `src/previews.ts` 2. Disable indexing at the platform Not per site. Someone will forget and it is found months later. Files: `src/serve.ts` 3. Clean up on a policy Without breaking permanent deploy addresses, which are the rollback path. Files: `src/cleanup.ts` ### Done when - [ ] Branches and pull requests get their own addresses - [ ] Previews are not indexable, enforced platform side - [ ] Cleanup does not break deploy addresses - [ ] A pull request reuses its preview address ### Watch out - Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. ## M5 · Domains, certificates and going live (production only) A custom domain works with a valid certificate and renews without intervention. ### Steps 1. Verify ownership before issuing And re-verify periodically, since records change. Files: `src/domains.ts` 2. Renew early and alert on failure An expired certificate is a site that browsers refuse to open. Files: `src/certificates.ts` 3. Document the record requirements Apex and subdomain differ and this is where users get stuck. Files: `docs/dns.md` ### Done when - [ ] A custom domain verifies and gets a valid certificate - [ ] Renewal failure alerts well before expiry - [ ] Apex and subdomain both work - [ ] Changed records are detected and reported ### Watch out - Automatic certificate renewal that fails silently produces a site presenting an expired certificate, which browsers block outright, and the first you hear is a customer telling you the site is down. Alert on renewal failure well before expiry and treat a failed renewal as an incident rather than a retry. ===== OPERATIONS.md ===== # Operations · Netlify ## Backup Manifests and deploy metadata are small and make every past version restorable; blobs are large and content addressed so they deduplicate well. Back up both, and note that losing manifests makes the blobs meaningless. ## Restore Restore and confirm the production deploy pointer and its manifest agree, since a restore that returns blobs without manifests leaves a store of files nobody can assemble into a site. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Certificate expiry, build queue depth and deploy switch duration. Certificate expiry is the one that takes a site fully offline, and it is entirely predictable, which makes missing it inexcusable. ## Incident checklist A half-updated page during a deploy means the switch was not atomic, and it is reproducible under load rather than by chance. An expired certificate is the other real incident and the alerting window is the control. 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 - [ ] No request during a deploy is confirmed to see a mixture - [ ] Rollback is confirmed to restore redirects with the files - [ ] A hostile build is confirmed unable to reach other sites - [ ] Previews are confirmed non-indexable at the platform level - [ ] Certificate renewal failure is confirmed to alert before expiry ## Launch constraint Do not market omitted Netlify 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 · secret. Sites, deploys and domain state. DATABASE_URL=postgres://localhost:5432/hosting # Required. Content-addressed files, shared across deploys. BLOB_BUCKET=s3://deploy-blobs # Required. Hard. A build that never finishes blocks the queue behind it. BUILD_TIMEOUT_SECONDS=900 # Required. Present as a constant. Indexed previews are found months later in search results. PREVIEW_INDEXABLE=never # Required. Permanent deploy addresses are what make rollback a pointer move. DEPLOY_RETENTION=forever # Required. Early, with alerting on failure. An expired certificate blocks the site entirely. CERT_RENEW_DAYS_BEFORE=30
# Netlify · indie build A hosting platform whose defining property is that a deploy is atomic: a site is never half old and half new, every deploy stays addressable forever, and rolling back is repointing rather than rebuilding. Estimated effort: **multi-day**. 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 | | --- | --- | --- | | Storage | Content-addressed files with an immutable manifest per deploy | identical files across deploys cost one copy and rollback is a pointer move | | Serving | A manifest lookup per request resolving to content by hash | atomicity comes from serving one manifest rather than a mutable directory | | Builds | Isolated build containers with a cache and a hard timeout | builds run arbitrary code and must not be able to affect anything else | | Previews | A deploy per branch and per pull request, addressable separately | reviewing a change is the feature that justifies the whole model | | Runtime | TypeScript with PostgreSQL and object storage | manifests and deploy history are relational, files are not | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the control plane, the serving layer and the build orchestrator. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free locally - Why: Deploy history, manifests and domain state. - Get it: Docker or your package manager. - Verify: psql connects and creates a table - [ ] **A container runtime for build isolation** · free - Why: Builds execute arbitrary code from a repository and running them on the host is not something to do even once. - Get it: Docker or an equivalent, with a plan for network and filesystem restriction per build. - Verify: A build container cannot read the host filesystem or reach an unexpected network address - [ ] **Object storage for content-addressed files** · pennies per month at small scale - Why: Blobs are stored once per distinct hash and referenced by many deploys, which is what makes keeping every deploy affordable. - Get it: Any provider with cheap storage and a lifecycle policy for cleanup. - Verify: A file uploads by hash and is retrievable by that hash - [ ] **A domain you control** · the price of a domain - Why: Certificate issuance, apex handling and renewal cannot be tested without a real domain and real records. - Get it: Any domain where you can change records, ideally with a subdomain free for testing. - Verify: A record change propagates and a certificate issues successfully ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Edge functions. - A global network. - Forms, identity and platform conveniences. - Multi-tenant scale and abuse handling. - deploy previews - CDN/edge functions - forms - rollbacks - team workflows - uptime/support If one of those is essential to you, that is the reason to keep paying for Netlify, and the README should say so rather than pretend.
# Build brief · Netlify 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 static site hosting with atomic deploys, previews per branch and instant rollback to replace Netlify. 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) - Storage: Content-addressed files with an immutable manifest per deploy · identical files across deploys cost one copy and rollback is a pointer move - Serving: A manifest lookup per request resolving to content by hash · atomicity comes from serving one manifest rather than a mutable directory - Builds: Isolated build containers with a cache and a hard timeout · builds run arbitrary code and must not be able to affect anything else - Previews: A deploy per branch and per pull request, addressable separately · reviewing a change is the feature that justifies the whole model - Runtime: TypeScript with PostgreSQL and object storage · manifests and deploy history are relational, files are not ### Data model (create this before Phase 1) - sites(id, name, production_deploy_id, custom_domains_json, build_config_json) - deploys(id, site_id, branch, commit_sha, state, manifest_id, built_at, build_log_path, context) · context is production, branch or preview - manifests(id, deploy_id, entries_json, entry_count, total_bytes) · path to content hash, immutable - blobs(content_hash, storage_key, bytes, first_seen_at, refcount) · one copy per distinct file - domains(id, site_id, hostname, certificate_id, verified_at, primary) - redirects(deploy_id, ordinal, from_pattern, to_target, status, conditions_json) · part of the deploy, not of the site - A redirect belongs to a deploy rather than to a site, because rolling back must roll back the routing rules too. ### Phase 1 · Deploys that are all or nothing Build: Upload files content addressed, build an immutable manifest, and switch the site to it in one operation. Serve by resolving the current manifest per request. Verify no request during a deploy sees a mixture. Trap: Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. Done when: Requests during a deploy return either entirely the old version or entirely the new one, verified under load during a switch. Identical files across deploys are stored once, verified by checking storage after two similar deploys. A manifest is immutable once written. A failed upload leaves the previous deploy serving unchanged. Do not build yet: No builds, previews, domains or rollback. ### Phase 2 · Rollback as a pointer move Build: Keep every deploy addressable permanently at its own address. Roll back by repointing the site at a previous manifest. Roll back redirects and headers with it, since they are part of the deploy. Trap: Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. Done when: Rolling back takes seconds and requires no rebuild, measured. Redirects and headers roll back with the files, verified by changing a redirect and reverting. Every past deploy remains addressable at its own permanent address. Rolling back and forward again produces byte-identical serving. Do not build yet: No builds, previews or domains yet. ### Phase 3 · Running someone else's build safely Build: Run builds in isolated containers with no access to other sites, the platform's own credentials or the network beyond declared dependencies. Enforce a hard timeout and a resource ceiling. Stream logs and keep them. Trap: A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. Done when: A build cannot read another site's files or the platform's credentials, tested with a deliberately hostile build script. Builds terminate at a hard timeout with the log preserved. Environment variables are scoped to the site and secrets are not printed in logs. A build cache speeds a repeat build without leaking between sites. Do not build yet: No previews or domains yet. ### Phase 4 · A preview per change Build: Deploy each branch and pull request to its own address, distinct from production. Prevent previews from being indexed. Clean up preview deploys on a policy without breaking the permanent deploy addresses. Trap: Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. Done when: Each branch and pull request deploys to its own address. Previews are not indexable, enforced at the platform level rather than by site configuration. Preview cleanup follows a policy without breaking the permanent per-deploy addresses. A pull request updates its preview on each push without creating a new address. Do not build yet: No domains, certificates or functions yet. ### Phase 5 · Domains, certificates and going live Build: Verify domain ownership, issue and renew certificates automatically, and handle the apex and subdomain cases. Alert well before expiry rather than at it. Handle a domain whose records were changed by someone else. Trap: Automatic certificate renewal that fails silently produces a site presenting an expired certificate, which browsers block outright, and the first you hear is a customer telling you the site is down. Alert on renewal failure well before expiry and treat a failed renewal as an incident rather than a retry. Done when: A custom domain verifies and receives a valid certificate. Renewal happens automatically and a failure alerts well before expiry rather than at it. Apex and subdomain configurations both work with their documented record requirements. A domain whose records changed is detected and reported rather than silently failing renewal. Do not build yet: Nothing after this. ### Out of scope (and why) - Serverless functions at the edge, which is a substantial runtime and isolation project of its own. - A global network. This runs from one place, and latency for distant visitors is the honest trade. - Form handling, identity and the other conveniences that make the hosted product a platform. - Multi-tenant scale and abuse handling, which is where a hosting business actually lives. ### README must contain - That a deploy is atomic because the manifest is built first and switched in one operation - That redirects and headers belong to the deploy so a rollback restores the routing too - That every deploy stays permanently addressable, which is what makes rollback a pointer move - That builds run arbitrary repository code and are isolated with no ambient platform credentials - That previews are non-indexable at the platform level rather than by site configuration
# Agent instructions · Netlify indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Content-addressed files with an immutable manifest per deploy, A manifest lookup per request resolving to content by hash, Isolated build containers with a cache and a hard timeout, A deploy per branch and per pull request, addressable separately, TypeScript with PostgreSQL and object storage. 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". ## Known traps - Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. - Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. - A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. - Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it.
# Build plan · Netlify A hosting platform whose defining property is that a deploy is atomic: a site is never half old and half new, every deploy stays addressable forever, and rolling back is repointing rather than rebuilding. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Deploys that are all or nothing A site is never partly updated, even mid-deploy. ### Steps 1. Content address every file Identical files cost one copy and rollback becomes a pointer move. Files: `src/blobs.ts` 2. Build the manifest before switching Uploading into a live directory is the half-updated page. Files: `src/manifest.ts` 3. Switch atomically and test under load During the switch, not before and after it. Files: `tests/atomic.test.ts` ### Done when - [ ] No request during a deploy sees a mixture - [ ] Identical files are stored once - [ ] Manifests are immutable - [ ] A failed upload leaves the previous deploy serving ### Watch out - Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. ## Phase 2 · Rollback as a pointer move Reverting to any previous deploy takes seconds and needs no rebuild. ### Steps 1. Keep deploys permanently addressable Each at its own address, forever. It costs almost nothing. Files: `src/deploys.ts` 2. Attach routing to the deploy A rollback with the new redirects is a combination nobody tested. Files: `src/redirects.ts` 3. Make rollback a pointer move Seconds, not a rebuild. Measure it. Files: `src/rollback.ts` ### Done when - [ ] Rollback takes seconds without rebuilding - [ ] Redirects and headers roll back with the files - [ ] Past deploys stay permanently addressable - [ ] Rolling back and forward is byte-identical ### Watch out - Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. ## Phase 3 · Running someone else's build safely A build runs arbitrary code without reaching anything it should not. ### Steps 1. Isolate per build with no ambient credentials A build runs arbitrary code from a repository. Files: `src/build/sandbox.ts`, `tests/build-isolation.test.ts` 2. Enforce timeouts and ceilings A build that never finishes is a resource leak with a queue behind it. Files: `src/build/limits.ts` 3. Keep logs and redact secrets Logs are read by people debugging and are the usual place secrets escape. Files: `src/build/logs.ts` ### Done when - [ ] A hostile build cannot reach other sites or platform credentials - [ ] Builds terminate at a hard timeout with logs kept - [ ] Secrets do not appear in logs - [ ] The build cache does not leak between sites ### Watch out - A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. ## Phase 4 · A preview per change Every branch and pull request gets its own permanent address. ### Steps 1. Address previews separately from production Predictable per branch and per pull request. Files: `src/previews.ts` 2. Disable indexing at the platform Not per site. Someone will forget and it is found months later. Files: `src/serve.ts` 3. Clean up on a policy Without breaking permanent deploy addresses, which are the rollback path. Files: `src/cleanup.ts` ### Done when - [ ] Branches and pull requests get their own addresses - [ ] Previews are not indexable, enforced platform side - [ ] Cleanup does not break deploy addresses - [ ] A pull request reuses its preview address ### Watch out - Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. ## Not in this build - Edge functions. - A global network. - Forms, identity and platform conveniences. - Multi-tenant scale and abuse handling. ## After v1, if you want it - Deploy notifications with the preview address, which is where the review workflow starts - Split testing between two deploys, which the manifest model makes straightforward - Build plugins with the same isolation guarantees, once the sandbox is proven
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Sites, deploys and domain state. DATABASE_URL=postgres://localhost:5432/hosting # Required. Content-addressed files, shared across deploys. BLOB_BUCKET=s3://deploy-blobs # Required. Hard. A build that never finishes blocks the queue behind it. BUILD_TIMEOUT_SECONDS=900 # Required. Present as a constant. Indexed previews are found months later in search results. PREVIEW_INDEXABLE=never # Required. Permanent deploy addresses are what make rollback a pointer move. DEPLOY_RETENTION=forever # Required. Early, with alerting on failure. An expired certificate blocks the site entirely. CERT_RENEW_DAYS_BEFORE=30
# Netlify · product brief ## Problem Static hosting and functions are easy to assemble, but Netlify's value is Git-based deploys, previews, forms, redirects, edge functions, and managed platform reliability. ## Product outcome Hosting where a deploy is never half applied, any past version is one pointer move away, and a build running arbitrary repository code cannot reach anything but its own site. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - VPS or cloud static hosting - CI/CD - domain/DNS - SSL - serverless or container functions - monitoring ## Explicit non-goals for v1 - Edge functions. - A global network. - Forms, identity and platform conveniences. - Multi-tenant scale and abuse handling. - deploy previews - CDN/edge functions - forms - rollbacks - team workflows - uptime/support ## Success criteria - No request during a deploy is confirmed to see a mixture - Rollback is confirmed to restore redirects with the files - A hostile build is confirmed unable to reach other sites - Previews are confirmed non-indexable at the platform level - Certificate renewal failure is confirmed to alert before expiry
# Build brief · Netlify 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 static site hosting with atomic deploys, previews per branch and instant rollback to replace Netlify. 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) - Storage: Content-addressed files with an immutable manifest per deploy · identical files across deploys cost one copy and rollback is a pointer move - Serving: A manifest lookup per request resolving to content by hash · atomicity comes from serving one manifest rather than a mutable directory - Builds: Isolated build containers with a cache and a hard timeout · builds run arbitrary code and must not be able to affect anything else - Previews: A deploy per branch and per pull request, addressable separately · reviewing a change is the feature that justifies the whole model - Runtime: TypeScript with PostgreSQL and object storage · manifests and deploy history are relational, files are not ### Data model (create this before Phase 1) - sites(id, name, production_deploy_id, custom_domains_json, build_config_json) - deploys(id, site_id, branch, commit_sha, state, manifest_id, built_at, build_log_path, context) · context is production, branch or preview - manifests(id, deploy_id, entries_json, entry_count, total_bytes) · path to content hash, immutable - blobs(content_hash, storage_key, bytes, first_seen_at, refcount) · one copy per distinct file - domains(id, site_id, hostname, certificate_id, verified_at, primary) - redirects(deploy_id, ordinal, from_pattern, to_target, status, conditions_json) · part of the deploy, not of the site - A redirect belongs to a deploy rather than to a site, because rolling back must roll back the routing rules too. ### Phase 1 · Deploys that are all or nothing Build: Upload files content addressed, build an immutable manifest, and switch the site to it in one operation. Serve by resolving the current manifest per request. Verify no request during a deploy sees a mixture. Trap: Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. Done when: Requests during a deploy return either entirely the old version or entirely the new one, verified under load during a switch. Identical files across deploys are stored once, verified by checking storage after two similar deploys. A manifest is immutable once written. A failed upload leaves the previous deploy serving unchanged. Do not build yet: No builds, previews, domains or rollback. ### Phase 2 · Rollback as a pointer move Build: Keep every deploy addressable permanently at its own address. Roll back by repointing the site at a previous manifest. Roll back redirects and headers with it, since they are part of the deploy. Trap: Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. Done when: Rolling back takes seconds and requires no rebuild, measured. Redirects and headers roll back with the files, verified by changing a redirect and reverting. Every past deploy remains addressable at its own permanent address. Rolling back and forward again produces byte-identical serving. Do not build yet: No builds, previews or domains yet. ### Phase 3 · Running someone else's build safely Build: Run builds in isolated containers with no access to other sites, the platform's own credentials or the network beyond declared dependencies. Enforce a hard timeout and a resource ceiling. Stream logs and keep them. Trap: A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. Done when: A build cannot read another site's files or the platform's credentials, tested with a deliberately hostile build script. Builds terminate at a hard timeout with the log preserved. Environment variables are scoped to the site and secrets are not printed in logs. A build cache speeds a repeat build without leaking between sites. Do not build yet: No previews or domains yet. ### Phase 4 · A preview per change Build: Deploy each branch and pull request to its own address, distinct from production. Prevent previews from being indexed. Clean up preview deploys on a policy without breaking the permanent deploy addresses. Trap: Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. Done when: Each branch and pull request deploys to its own address. Previews are not indexable, enforced at the platform level rather than by site configuration. Preview cleanup follows a policy without breaking the permanent per-deploy addresses. A pull request updates its preview on each push without creating a new address. Do not build yet: No domains, certificates or functions yet. ### Phase 5 · Domains, certificates and going live Build: Verify domain ownership, issue and renew certificates automatically, and handle the apex and subdomain cases. Alert well before expiry rather than at it. Handle a domain whose records were changed by someone else. Trap: Automatic certificate renewal that fails silently produces a site presenting an expired certificate, which browsers block outright, and the first you hear is a customer telling you the site is down. Alert on renewal failure well before expiry and treat a failed renewal as an incident rather than a retry. Done when: A custom domain verifies and receives a valid certificate. Renewal happens automatically and a failure alerts well before expiry rather than at it. Apex and subdomain configurations both work with their documented record requirements. A domain whose records changed is detected and reported rather than silently failing renewal. Do not build yet: Nothing after this. ### Out of scope (and why) - Serverless functions at the edge, which is a substantial runtime and isolation project of its own. - A global network. This runs from one place, and latency for distant visitors is the honest trade. - Form handling, identity and the other conveniences that make the hosted product a platform. - Multi-tenant scale and abuse handling, which is where a hosting business actually lives. ### README must contain - That a deploy is atomic because the manifest is built first and switched in one operation - That redirects and headers belong to the deploy so a rollback restores the routing too - That every deploy stays permanently addressable, which is what makes rollback a pointer move - That builds run arbitrary repository code and are isolated with no ambient platform credentials - That previews are non-indexable at the platform level rather than by site configuration
# Architecture · Netlify ## Stack | Part | Choice | Why | | --- | --- | --- | | Storage | Content-addressed files with an immutable manifest per deploy | identical files across deploys cost one copy and rollback is a pointer move | | Serving | A manifest lookup per request resolving to content by hash | atomicity comes from serving one manifest rather than a mutable directory | | Builds | Isolated build containers with a cache and a hard timeout | builds run arbitrary code and must not be able to affect anything else | | Previews | A deploy per branch and per pull request, addressable separately | reviewing a change is the feature that justifies the whole model | | Runtime | TypeScript with PostgreSQL and object storage | manifests and deploy history are relational, files are not | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Blobs | Content-addressed file storage. | Any store, provided identity is the content hash. | | Manifests | Immutable per-deploy file maps. | Not swappable for a mutable directory, which is the half-updated page. | | Builds | Isolated execution with limits. | Any runtime, provided no ambient platform credentials are present. | | Previews | Per-branch addresses that are not indexable. | Any scheme, provided indexing is disabled at the platform. | | Domains | Verification, certificates and renewal alerting. | Any issuer, provided renewal failure alerts before expiry. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · Sites, deploys and domain state. - `BLOB_BUCKET` · required · Content-addressed files, shared across deploys. - `BUILD_TIMEOUT_SECONDS` · required · Hard. A build that never finishes blocks the queue behind it. - `PREVIEW_INDEXABLE` · required · Present as a constant. Indexed previews are found months later in search results. - `DEPLOY_RETENTION` · required · Permanent deploy addresses are what make rollback a pointer move. - `CERT_RENEW_DAYS_BEFORE` · required · Early, with alerting on failure. An expired certificate blocks the site entirely. ## 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 · Netlify product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Content-addressed files with an immutable manifest per deploy, A manifest lookup per request resolving to content by hash, Isolated build containers with a cache and a hard timeout, A deploy per branch and per pull request, addressable separately, TypeScript with PostgreSQL and object storage. - 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. ## Known traps - Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. - Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. - A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. - Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. - Automatic certificate renewal that fails silently produces a site presenting an expired certificate, which browsers block outright, and the first you hear is a customer telling you the site is down. Alert on renewal failure well before expiry and treat a failed renewal as an incident rather than a retry.
# Delivery milestones · Netlify Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Deploys that are all or nothing A site is never partly updated, even mid-deploy. ### Steps 1. Content address every file Identical files cost one copy and rollback becomes a pointer move. Files: `src/blobs.ts` 2. Build the manifest before switching Uploading into a live directory is the half-updated page. Files: `src/manifest.ts` 3. Switch atomically and test under load During the switch, not before and after it. Files: `tests/atomic.test.ts` ### Done when - [ ] No request during a deploy sees a mixture - [ ] Identical files are stored once - [ ] Manifests are immutable - [ ] A failed upload leaves the previous deploy serving ### Watch out - Uploading files into a live directory means a visitor during the deploy gets new markup referencing assets that have not arrived yet, which manifests as a broken page for a few seconds and is nearly impossible to reproduce afterwards. Build the manifest first and switch to it atomically. ## M2 · Rollback as a pointer move Reverting to any previous deploy takes seconds and needs no rebuild. ### Steps 1. Keep deploys permanently addressable Each at its own address, forever. It costs almost nothing. Files: `src/deploys.ts` 2. Attach routing to the deploy A rollback with the new redirects is a combination nobody tested. Files: `src/redirects.ts` 3. Make rollback a pointer move Seconds, not a rebuild. Measure it. Files: `src/rollback.ts` ### Done when - [ ] Rollback takes seconds without rebuilding - [ ] Redirects and headers roll back with the files - [ ] Past deploys stay permanently addressable - [ ] Rolling back and forward is byte-identical ### Watch out - Storing redirects and headers against the site rather than the deploy means a rollback restores the old files with the new routing rules, producing a combination that never existed and was never tested. Attach routing configuration to the deploy so a rollback is complete. ## M3 · Running someone else's build safely A build runs arbitrary code without reaching anything it should not. ### Steps 1. Isolate per build with no ambient credentials A build runs arbitrary code from a repository. Files: `src/build/sandbox.ts`, `tests/build-isolation.test.ts` 2. Enforce timeouts and ceilings A build that never finishes is a resource leak with a queue behind it. Files: `src/build/limits.ts` 3. Keep logs and redact secrets Logs are read by people debugging and are the usual place secrets escape. Files: `src/build/logs.ts` ### Done when - [ ] A hostile build cannot reach other sites or platform credentials - [ ] Builds terminate at a hard timeout with logs kept - [ ] Secrets do not appear in logs - [ ] The build cache does not leak between sites ### Watch out - A build step runs whatever the repository's configuration says, which is arbitrary code, and a build container with platform credentials in its environment hands them to anyone who can open a pull request. Isolate per build, inject only that site's own declared variables, and never the platform's. ## M4 · A preview per change Every branch and pull request gets its own permanent address. ### Steps 1. Address previews separately from production Predictable per branch and per pull request. Files: `src/previews.ts` 2. Disable indexing at the platform Not per site. Someone will forget and it is found months later. Files: `src/serve.ts` 3. Clean up on a policy Without breaking permanent deploy addresses, which are the rollback path. Files: `src/cleanup.ts` ### Done when - [ ] Branches and pull requests get their own addresses - [ ] Previews are not indexable, enforced platform side - [ ] Cleanup does not break deploy addresses - [ ] A pull request reuses its preview address ### Watch out - Preview deploys indexed by search engines put unreleased content and duplicate pages into search results, which is discovered months later by someone finding a staging page ahead of the real one. Serve previews with indexing disabled at the platform level rather than trusting each site to configure it. ## M5 · Domains, certificates and going live (production only) A custom domain works with a valid certificate and renews without intervention. ### Steps 1. Verify ownership before issuing And re-verify periodically, since records change. Files: `src/domains.ts` 2. Renew early and alert on failure An expired certificate is a site that browsers refuse to open. Files: `src/certificates.ts` 3. Document the record requirements Apex and subdomain differ and this is where users get stuck. Files: `docs/dns.md` ### Done when - [ ] A custom domain verifies and gets a valid certificate - [ ] Renewal failure alerts well before expiry - [ ] Apex and subdomain both work - [ ] Changed records are detected and reported ### Watch out - Automatic certificate renewal that fails silently produces a site presenting an expired certificate, which browsers block outright, and the first you hear is a customer telling you the site is down. Alert on renewal failure well before expiry and treat a failed renewal as an incident rather than a retry.
# Operations · Netlify ## Backup Manifests and deploy metadata are small and make every past version restorable; blobs are large and content addressed so they deduplicate well. Back up both, and note that losing manifests makes the blobs meaningless. ## Restore Restore and confirm the production deploy pointer and its manifest agree, since a restore that returns blobs without manifests leaves a store of files nobody can assemble into a site. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Certificate expiry, build queue depth and deploy switch duration. Certificate expiry is the one that takes a site fully offline, and it is entirely predictable, which makes missing it inexcusable. ## Incident checklist A half-updated page during a deploy means the switch was not atomic, and it is reproducible under load rather than by chance. An expired certificate is the other real incident and the alerting window is the control. 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 - [ ] No request during a deploy is confirmed to see a mixture - [ ] Rollback is confirmed to restore redirects with the files - [ ] A hostile build is confirmed unable to reach other sites - [ ] Previews are confirmed non-indexable at the platform level - [ ] Certificate renewal failure is confirmed to alert before expiry ## Launch constraint Do not market omitted Netlify 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 · secret. Sites, deploys and domain state. DATABASE_URL=postgres://localhost:5432/hosting # Required. Content-addressed files, shared across deploys. BLOB_BUCKET=s3://deploy-blobs # Required. Hard. A build that never finishes blocks the queue behind it. BUILD_TIMEOUT_SECONDS=900 # Required. Present as a constant. Indexed previews are found months later in search results. PREVIEW_INDEXABLE=never # Required. Permanent deploy addresses are what make rollback a pointer move. DEPLOY_RETENTION=forever # Required. Early, with alerting on failure. An expired certificate blocks the site entirely. CERT_RENEW_DAYS_BEFORE=30
$ choose a build depth, inspect the files, then open the complete pack in your agent
They pay because simple sites still need dependable CI/CD, routing, and previews.
xdeploy previews
xCDN/edge functions
xforms
xrollbacks
xteam workflows
xuptime/support
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Netlify pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0/workspace | $0/workspace | 300 credits/month hard cap; 1 member; 1 concurrent build; 1-day observability/analytics history. |
| personal | $9/workspace | — | 1,000 credits/month; 1 member; 1 concurrent build; 1 preview server; 7-day web analytics. |
| pro 3,000 credits | $20/workspace | — | 3,000 credits/month; unlimited members; 3 concurrent builds; 30-day analytics; no monthly-credit rollover. |
| pro 5,000 credits | $33/workspace | — | 5,000 credits/month; unlimited members; 3 concurrent builds; unused monthly credits roll for 1 additional cycle. |
| pro 10,000 credits | $63/workspace | — | 10,000 credits/month; unlimited members; 3 concurrent builds; unused monthly credits roll for 1 additional cycle. |
| pro 15,000 credits | $95/workspace | — | 15,000 credits/month; unlimited members; 3 concurrent builds; unused monthly credits roll for 1 additional cycle. |
| pro 20,000 credits | $126/workspace | — | 20,000 credits/month; unlimited members; 3 concurrent builds; unused monthly credits roll for 1 additional cycle. |
| enterprise | custom | — | Custom credits, seats, builds, security, SLA and support. |
free tier300 credits/month hard cap, 1 member, 1 concurrent build, and 1-day observability/analytics history
billingmonthly only for current credit-based self-serve plans; no public annual plan
hidden costsProduction deploys cost 15 credits, compute 10 credits/GB-hour, bandwidth 20 credits/GB and web requests 2 credits/10,000. Pro auto-recharge is $10/1,500 credits; extra concurrent builds cost $40/month and extra preview servers $15/month. Changing a Pro credit tier deletes unused plan and rollover credits.
verified 2026-08-14 · source ↗
Vibecode Netlify
Kinda. The core of Netlify is buildable in a weekend with the prompt on this page, but there are real gaps: deploy previews, CDN/edge functions. Read the honest list above before committing.
How much does Netlify cost?
Netlify costs about $20/month (Pro, checked 2026-07-30), which is $240 per year.
What do I lose by replacing Netlify?
Honestly: deploy previews; CDN/edge functions; forms; rollbacks; team workflows; uptime/support. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Netlify?
Yes: Netlify Free (The same service covers a small production site indefinitely: Git deploys, previews, functions, forms, TLS, and 300 monthly credits.) The prompt is for when you want it exactly your way.