Vibecode Replit
track this build5 phases, 15 steps, beginner friendly0%⌨️ featured in the best vibe coding tools
A dev container plus code-server plus deployment can replace some value, but Replit's beginner-friendly cloud IDE, agents, secrets, hosting, and zero-setup environment are a platform.
You are building a lean indie version of Replit. 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 ===== # Replit · indie build code-server behind authentication and TLS, one container per project so a runaway process cannot take the machine down, secrets injected at runtime rather than written into workspaces, a template system for new projects, and a deploy command that publishes a project to a real URL. 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 | | --- | --- | --- | | Editor | code-server, self-hosted | Visual Studio Code in a browser, with the extension ecosystem already there | | Isolation | One container per project with memory and process limits | the difference between one project hanging and the whole server hanging | | Routing | Caddy in front, one subdomain per project | automatic certificates and a clean separation between projects | | Secrets | Injected as environment variables at container start, never written to disk in the workspace | a secret in a workspace file gets committed, and workspaces are exactly what people commit | | Persistence | A volume per project, backed up | the workspace is the user's work and must outlive the container | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Linux server with room for several containers** · 10 to 40 USD a month depending on how many projects run at once - Why: Each project is a container with its own memory allowance, so capacity is the number of projects multiplied by their limits. - Get it: Any virtual server provider. Start with something modest and size up once you know how many projects you actually keep. - Verify: free -m shows enough memory for your intended project count and limits - [ ] **Docker Engine with Compose v2** · free - Why: Isolation, limits and lifecycle all come from the container runtime. - Get it: Install from Docker's own repository rather than a distribution package, which is often behind. - Verify: docker compose version reports v2 or higher - [ ] **A domain with a wildcard DNS record** · roughly 10 to 15 USD a year - Why: Each project gets a subdomain, and a wildcard certificate needs a DNS challenge with a provider API token. - Get it: Any registrar with DNS API access. Add a wildcard record and create a scoped token. - Verify: A wildcard hostname resolves and the token can create a TXT record - [ ] **An honest assessment of who can reach this** · free - Why: A browser development environment is a remote shell. Deciding whether it is only you, a small trusted group, or anyone changes every isolation decision. - Get it: Write down who will have accounts and what happens if one is compromised. If the answer is anything beyond a small trusted group, revisit the isolation model before Phase 2. - Verify: The access decision is written down before Phase 1 is deployed - [ ] **An off-server backup destination** · a few USD a month - Why: Workspaces are people's work and the secret store exists nowhere else. Both die with the server otherwise. - Get it: Object storage with credentials scoped to one bucket. - Verify: A test file uploads and lists from your laptop ## Quick start ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable nmap -Pn YOUR_SERVER_IP ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The zero-setup experience, since setup is now yours. - Hosted agents that run without your machine. - Multiplayer editing in the browser. - Instant scaling under load. - zero-setup browser IDE - hosted agents - multiplayer - templates - deployments - education/community workflows If one of those is essential to you, that is the reason to keep paying for Replit, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Replit 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 a browser development environment on your own server, with isolated projects and one-command deploys to replace Replit. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write a browser IDE. code-server is Visual Studio Code compiled to run on a server and reached through a browser, and it already works. This is an operations task: run it safely, isolate projects from each other, handle secrets, and give yourself a deploy path. Take the isolation seriously. This service exists to run arbitrary code from a browser, which is the same thing an attacker wants. Every phase below treats that as the primary constraint rather than an afterthought. ### Stack (fixed, do not substitute) - Editor: code-server, self-hosted · Visual Studio Code in a browser, with the extension ecosystem already there - Isolation: One container per project with memory and process limits · the difference between one project hanging and the whole server hanging - Routing: Caddy in front, one subdomain per project · automatic certificates and a clean separation between projects - Secrets: Injected as environment variables at container start, never written to disk in the workspace · a secret in a workspace file gets committed, and workspaces are exactly what people commit - Persistence: A volume per project, backed up · the workspace is the user's work and must outlive the container ### Data model (create this before Phase 1) - This is infrastructure, so the state worth writing down is operational. - projects(id, name, subdomain, image, cpu_limit, memory_limit, created_at, last_active_at) - volumes · one per project, holding the workspace, backed up on a schedule - secrets · per project, stored encrypted outside any workspace, injected at container start - templates · base images and starter files per language - sessions · who is authenticated, and to which projects ### Phase 1 · One editor, behind a locked door Build: Provision a server, install Docker, run code-server in a container bound to localhost, and put Caddy in front for a certificate and authentication. Do not expose it directly at any point, not even briefly during setup. Trap: A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. Done when: The editor loads over HTTPS after authentication. An unauthenticated request is refused before reaching code-server. A port scan from another machine shows only SSH and the web ports. The editor's own port is not reachable from outside. Do not build yet: No isolation, no secrets, no deploys. ### Phase 2 · One container per project Build: A container per project with its own volume, memory limit, process limit and no privileged access. Route each project to its own subdomain. Prove the isolation by deliberately trying to exhaust memory and to read another project's files. Trap: A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. Done when: A deliberate memory exhaustion in one project kills that container and leaves other projects and the host responsive. A fork bomb hits the process limit rather than the host. Reading another project's volume from inside a container fails. Each project is reachable at its own subdomain with its own certificate. Do not build yet: No secrets and no deploys yet. ### Phase 3 · Secrets that do not end up in git Build: Store secrets per project outside every workspace, encrypted at rest. Inject them as environment variables when the container starts. Provide a command to set and list them, and make listing show names rather than values. Trap: A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. Done when: A secret is available as an environment variable inside the project and is absent from every file in the workspace, verified by a recursive grep. Listing secrets shows names and not values. Restarting the container re-injects them. A secret removed from the store is gone from the environment after a restart. Do not build yet: No templates or deploys yet. ### Phase 4 · Templates and a new project in one command Build: Base images per language with the runtime and common tooling preinstalled. A command that creates a project from a template: volume, container, subdomain, certificate and starter files. Time it, because the whole value here is that it is fast. Trap: Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. Done when: Creating a project from a template produces a working editor with the runtime available in under a minute. The starter project runs without any additional installation. Deleting a project removes its container, volume, subdomain and secrets together, verified by checking each. Do not build yet: No deploy path yet. ### Phase 5 · Deploying what you built Build: A deploy command that builds the project into an image, runs it as a separate container from the development one, and routes a public subdomain to it. Keep the previous deployment so a rollback is immediate. Trap: Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. Done when: A deploy produces a public URL serving the project, and the development editor stays private. A broken deploy leaves the previous version serving. Rollback restores the previous deployment in under a minute. The deployed container has no editor and no workspace volume attached. Do not build yet: Backups and lifecycle come last. ### Phase 6 · Backups, idle shutdown and the restore drill Build: Back up every project volume and the secret store off the machine. Stop containers that have been idle, and start them again on the next request. Restore a project onto a clean server and confirm the workspace and its secrets both came back. Trap: Backing up volumes without the secret store gives you a restore where every project is present and none of them can reach their APIs, and the secrets exist nowhere else by design. Back up both, and treat the secret store as the more important half. Done when: Project volumes and the secret store both land off the server nightly. An idle project's container stops and restarts on the next request without losing workspace contents. A restore onto a clean server reproduces a project with its files and its secrets, and the date is recorded. Do not build yet: Nothing after this. ### Out of scope (and why) - The zero-setup experience. Someone still has to run this server, patch it and pay for it, and that someone is now you. - Hosted agents that keep working when your browser is closed, which is compute you would be renting anyway. - Multiplayer editing in the browser, which is a substantial engineering effort on top of the editor. - The instant scaling that makes a hosted platform feel free at small sizes and expensive at large ones. ### README must contain - That this service runs arbitrary code and isolation is the primary constraint, not a feature - The per-project limits on memory, processes and files, and how each was tested - That secrets are injected at container start and never written into a workspace - That deployments are separate containers, and the development editor is never public - The backup schedule for volumes and the secret store, and the date of the last restore ===== AGENTS.md ===== # Agent instructions · Replit indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: code-server, self-hosted, One container per project with memory and process limits, Caddy in front, one subdomain per project, Injected as environment variables at container start, never written to disk in the workspace, A volume per project, backed up. 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 - A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. - A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. - A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. - Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. - Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. ===== BUILD_PLAN.md ===== # Build plan · Replit code-server behind authentication and TLS, one container per project so a runaway process cannot take the machine down, secrets injected at runtime rather than written into workspaces, a template system for new projects, and a deploy command that publishes a project to a real URL. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · One editor, behind a locked door code-server runs and is reachable only after authentication, over TLS. ### Steps 1. Provision and close the server Non-root user, key-only SSH, firewall allowing SSH and the web ports. Verify with a scan from elsewhere. ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable nmap -Pn YOUR_SERVER_IP ``` 2. Run code-server bound to localhost Publish only to 127.0.0.1 so nothing reaches it except through the proxy. ```sh docker run -d -p 127.0.0.1:8080:8080 -v ~/project:/home/coder/project codercom/code-server:latest ``` 3. Put Caddy in front with authentication Certificate and an authentication layer. Test the unauthenticated path before celebrating the authenticated one. Files: `Caddyfile` ### Done when - [ ] The editor loads over HTTPS after authentication - [ ] An unauthenticated request is refused - [ ] The editor port is unreachable from another machine - [ ] Only intended ports are open, verified by a scan ### Watch out - A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. ## Phase 2 · One container per project A runaway process in one project cannot take down the server or reach another project. ### Steps 1. Give every project limits Memory, process count, open files and CPU shares. Defaults are unlimited, which is the problem. ```sh docker run -d --memory=1g --pids-limit=200 --cpus=1 ... ``` 2. Separate volumes and drop privileges One volume per project, no privileged flag, no host mounts, a non-root user inside. Files: `compose/project.yml` 3. Test isolation adversarially Exhaust memory, fork bomb, and attempt to read a sibling project. Each should fail in a contained way. ### Done when - [ ] Memory exhaustion kills only its own container - [ ] A fork bomb hits the process limit rather than the host - [ ] A container cannot read another project's volume - [ ] Each project has its own subdomain and certificate ### Watch out - A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. ## Phase 3 · Secrets that do not end up in git A project can use an API key without that key being anywhere in the workspace. ### Steps 1. Store secrets outside workspaces Encrypted at rest, per project, on the host or in a secret manager. Never inside the volume. Files: `src/secrets.ts` 2. Inject at container start Environment variables passed to the container. Nothing is written into the workspace at any point. Files: `compose/project.yml` 3. Prove the absence A recursive grep of the workspace for the secret value, run as a test. Absence is the feature. Files: `test/secrets.sh` ### Done when - [ ] A secret is available as an environment variable in the project - [ ] A recursive grep of the workspace does not find the value - [ ] Listing secrets shows names only - [ ] Removing a secret clears it after a restart ### Watch out - A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. ## Phase 4 · Templates and a new project in one command Starting a project takes seconds rather than an afternoon of setup. ### Steps 1. Build base images per language Runtime, package manager and common tools baked in. Rebuild on a schedule to pick up security updates. Files: `images/` 2. Write the create command Volume, container, route, certificate and starter files in one action, reporting how long it took. Files: `src/create.ts` 3. Make deletion complete Container, volume, route, certificate and secrets. A partial delete leaves a subdomain pointing at nothing and secrets nobody is using. Files: `src/delete.ts` ### Done when - [ ] A new project is ready in under a minute - [ ] The starter project runs with no additional installation - [ ] Deleting removes container, volume, route and secrets - [ ] Base images are rebuilt on a schedule ### Watch out - Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. ## Phase 5 · Deploying what you built A project goes from editor to a public URL with one command. ### Steps 1. Build a separate deployment image From the project source, not from the development container. Different container, different limits, no editor. Files: `src/deploy.ts` 2. Route a public subdomain Separate from the development subdomain, with its own certificate. Files: `Caddyfile` 3. Keep the previous deployment Retain the prior image and a rollback command. Test by deploying something broken. Files: `src/rollback.ts` ### Done when - [ ] A deploy produces a working public URL - [ ] The development editor remains private - [ ] A broken deploy leaves the previous version serving - [ ] The deployed container has no editor or workspace attached ### Watch out - Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. ## Not in this build - The zero-setup experience, since setup is now yours. - Hosted agents that run without your machine. - Multiplayer editing in the browser. - Instant scaling under load. ## After v1, if you want it - Ephemeral preview environments created per branch - A shared base image cache to cut project creation time further - Per-project resource usage reporting, so limits can be set from evidence ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Projects are served at subdomains of this. BASE_DOMAIN=dev.example.com # Required · secret. Needed for wildcard certificate issuance through a DNS challenge. DNS_API_TOKEN=replace-with-provider-token # Required. Per-container memory ceiling. Unset means unlimited, which is how one project takes down the host. PROJECT_MEMORY_LIMIT=1g # Required. Per-container process limit. This is what stops a fork bomb. PROJECT_PIDS_LIMIT=200 # Required · secret. Encrypts the secret store at rest. Losing it loses every project's secrets, so it belongs in your password manager and in the backup. SECRET_STORE_KEY=generate-a-long-random-value # Required. How long a project may sit idle before its container is stopped. IDLE_STOP_MINUTES=60 # Required. Where volumes and the secret store are copied nightly. BACKUP_DEST=s3://your-bucket/dev
You are building a lean indie version of Replit. 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 ===== # Replit · indie build code-server behind authentication and TLS, one container per project so a runaway process cannot take the machine down, secrets injected at runtime rather than written into workspaces, a template system for new projects, and a deploy command that publishes a project to a real URL. 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 | | --- | --- | --- | | Editor | code-server, self-hosted | Visual Studio Code in a browser, with the extension ecosystem already there | | Isolation | One container per project with memory and process limits | the difference between one project hanging and the whole server hanging | | Routing | Caddy in front, one subdomain per project | automatic certificates and a clean separation between projects | | Secrets | Injected as environment variables at container start, never written to disk in the workspace | a secret in a workspace file gets committed, and workspaces are exactly what people commit | | Persistence | A volume per project, backed up | the workspace is the user's work and must outlive the container | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Linux server with room for several containers** · 10 to 40 USD a month depending on how many projects run at once - Why: Each project is a container with its own memory allowance, so capacity is the number of projects multiplied by their limits. - Get it: Any virtual server provider. Start with something modest and size up once you know how many projects you actually keep. - Verify: free -m shows enough memory for your intended project count and limits - [ ] **Docker Engine with Compose v2** · free - Why: Isolation, limits and lifecycle all come from the container runtime. - Get it: Install from Docker's own repository rather than a distribution package, which is often behind. - Verify: docker compose version reports v2 or higher - [ ] **A domain with a wildcard DNS record** · roughly 10 to 15 USD a year - Why: Each project gets a subdomain, and a wildcard certificate needs a DNS challenge with a provider API token. - Get it: Any registrar with DNS API access. Add a wildcard record and create a scoped token. - Verify: A wildcard hostname resolves and the token can create a TXT record - [ ] **An honest assessment of who can reach this** · free - Why: A browser development environment is a remote shell. Deciding whether it is only you, a small trusted group, or anyone changes every isolation decision. - Get it: Write down who will have accounts and what happens if one is compromised. If the answer is anything beyond a small trusted group, revisit the isolation model before Phase 2. - Verify: The access decision is written down before Phase 1 is deployed - [ ] **An off-server backup destination** · a few USD a month - Why: Workspaces are people's work and the secret store exists nowhere else. Both die with the server otherwise. - Get it: Object storage with credentials scoped to one bucket. - Verify: A test file uploads and lists from your laptop ## Quick start ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable nmap -Pn YOUR_SERVER_IP ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The zero-setup experience, since setup is now yours. - Hosted agents that run without your machine. - Multiplayer editing in the browser. - Instant scaling under load. - zero-setup browser IDE - hosted agents - multiplayer - templates - deployments - education/community workflows If one of those is essential to you, that is the reason to keep paying for Replit, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Replit 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 a browser development environment on your own server, with isolated projects and one-command deploys to replace Replit. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write a browser IDE. code-server is Visual Studio Code compiled to run on a server and reached through a browser, and it already works. This is an operations task: run it safely, isolate projects from each other, handle secrets, and give yourself a deploy path. Take the isolation seriously. This service exists to run arbitrary code from a browser, which is the same thing an attacker wants. Every phase below treats that as the primary constraint rather than an afterthought. ### Stack (fixed, do not substitute) - Editor: code-server, self-hosted · Visual Studio Code in a browser, with the extension ecosystem already there - Isolation: One container per project with memory and process limits · the difference between one project hanging and the whole server hanging - Routing: Caddy in front, one subdomain per project · automatic certificates and a clean separation between projects - Secrets: Injected as environment variables at container start, never written to disk in the workspace · a secret in a workspace file gets committed, and workspaces are exactly what people commit - Persistence: A volume per project, backed up · the workspace is the user's work and must outlive the container ### Data model (create this before Phase 1) - This is infrastructure, so the state worth writing down is operational. - projects(id, name, subdomain, image, cpu_limit, memory_limit, created_at, last_active_at) - volumes · one per project, holding the workspace, backed up on a schedule - secrets · per project, stored encrypted outside any workspace, injected at container start - templates · base images and starter files per language - sessions · who is authenticated, and to which projects ### Phase 1 · One editor, behind a locked door Build: Provision a server, install Docker, run code-server in a container bound to localhost, and put Caddy in front for a certificate and authentication. Do not expose it directly at any point, not even briefly during setup. Trap: A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. Done when: The editor loads over HTTPS after authentication. An unauthenticated request is refused before reaching code-server. A port scan from another machine shows only SSH and the web ports. The editor's own port is not reachable from outside. Do not build yet: No isolation, no secrets, no deploys. ### Phase 2 · One container per project Build: A container per project with its own volume, memory limit, process limit and no privileged access. Route each project to its own subdomain. Prove the isolation by deliberately trying to exhaust memory and to read another project's files. Trap: A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. Done when: A deliberate memory exhaustion in one project kills that container and leaves other projects and the host responsive. A fork bomb hits the process limit rather than the host. Reading another project's volume from inside a container fails. Each project is reachable at its own subdomain with its own certificate. Do not build yet: No secrets and no deploys yet. ### Phase 3 · Secrets that do not end up in git Build: Store secrets per project outside every workspace, encrypted at rest. Inject them as environment variables when the container starts. Provide a command to set and list them, and make listing show names rather than values. Trap: A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. Done when: A secret is available as an environment variable inside the project and is absent from every file in the workspace, verified by a recursive grep. Listing secrets shows names and not values. Restarting the container re-injects them. A secret removed from the store is gone from the environment after a restart. Do not build yet: No templates or deploys yet. ### Phase 4 · Templates and a new project in one command Build: Base images per language with the runtime and common tooling preinstalled. A command that creates a project from a template: volume, container, subdomain, certificate and starter files. Time it, because the whole value here is that it is fast. Trap: Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. Done when: Creating a project from a template produces a working editor with the runtime available in under a minute. The starter project runs without any additional installation. Deleting a project removes its container, volume, subdomain and secrets together, verified by checking each. Do not build yet: No deploy path yet. ### Phase 5 · Deploying what you built Build: A deploy command that builds the project into an image, runs it as a separate container from the development one, and routes a public subdomain to it. Keep the previous deployment so a rollback is immediate. Trap: Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. Done when: A deploy produces a public URL serving the project, and the development editor stays private. A broken deploy leaves the previous version serving. Rollback restores the previous deployment in under a minute. The deployed container has no editor and no workspace volume attached. Do not build yet: Backups and lifecycle come last. ### Phase 6 · Backups, idle shutdown and the restore drill Build: Back up every project volume and the secret store off the machine. Stop containers that have been idle, and start them again on the next request. Restore a project onto a clean server and confirm the workspace and its secrets both came back. Trap: Backing up volumes without the secret store gives you a restore where every project is present and none of them can reach their APIs, and the secrets exist nowhere else by design. Back up both, and treat the secret store as the more important half. Done when: Project volumes and the secret store both land off the server nightly. An idle project's container stops and restarts on the next request without losing workspace contents. A restore onto a clean server reproduces a project with its files and its secrets, and the date is recorded. Do not build yet: Nothing after this. ### Out of scope (and why) - The zero-setup experience. Someone still has to run this server, patch it and pay for it, and that someone is now you. - Hosted agents that keep working when your browser is closed, which is compute you would be renting anyway. - Multiplayer editing in the browser, which is a substantial engineering effort on top of the editor. - The instant scaling that makes a hosted platform feel free at small sizes and expensive at large ones. ### README must contain - That this service runs arbitrary code and isolation is the primary constraint, not a feature - The per-project limits on memory, processes and files, and how each was tested - That secrets are injected at container start and never written into a workspace - That deployments are separate containers, and the development editor is never public - The backup schedule for volumes and the secret store, and the date of the last restore ===== AGENTS.md ===== # Agent instructions · Replit indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: code-server, self-hosted, One container per project with memory and process limits, Caddy in front, one subdomain per project, Injected as environment variables at container start, never written to disk in the workspace, A volume per project, backed up. 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 - A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. - A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. - A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. - Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. - Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. ===== BUILD_PLAN.md ===== # Build plan · Replit code-server behind authentication and TLS, one container per project so a runaway process cannot take the machine down, secrets injected at runtime rather than written into workspaces, a template system for new projects, and a deploy command that publishes a project to a real URL. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · One editor, behind a locked door code-server runs and is reachable only after authentication, over TLS. ### Steps 1. Provision and close the server Non-root user, key-only SSH, firewall allowing SSH and the web ports. Verify with a scan from elsewhere. ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable nmap -Pn YOUR_SERVER_IP ``` 2. Run code-server bound to localhost Publish only to 127.0.0.1 so nothing reaches it except through the proxy. ```sh docker run -d -p 127.0.0.1:8080:8080 -v ~/project:/home/coder/project codercom/code-server:latest ``` 3. Put Caddy in front with authentication Certificate and an authentication layer. Test the unauthenticated path before celebrating the authenticated one. Files: `Caddyfile` ### Done when - [ ] The editor loads over HTTPS after authentication - [ ] An unauthenticated request is refused - [ ] The editor port is unreachable from another machine - [ ] Only intended ports are open, verified by a scan ### Watch out - A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. ## Phase 2 · One container per project A runaway process in one project cannot take down the server or reach another project. ### Steps 1. Give every project limits Memory, process count, open files and CPU shares. Defaults are unlimited, which is the problem. ```sh docker run -d --memory=1g --pids-limit=200 --cpus=1 ... ``` 2. Separate volumes and drop privileges One volume per project, no privileged flag, no host mounts, a non-root user inside. Files: `compose/project.yml` 3. Test isolation adversarially Exhaust memory, fork bomb, and attempt to read a sibling project. Each should fail in a contained way. ### Done when - [ ] Memory exhaustion kills only its own container - [ ] A fork bomb hits the process limit rather than the host - [ ] A container cannot read another project's volume - [ ] Each project has its own subdomain and certificate ### Watch out - A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. ## Phase 3 · Secrets that do not end up in git A project can use an API key without that key being anywhere in the workspace. ### Steps 1. Store secrets outside workspaces Encrypted at rest, per project, on the host or in a secret manager. Never inside the volume. Files: `src/secrets.ts` 2. Inject at container start Environment variables passed to the container. Nothing is written into the workspace at any point. Files: `compose/project.yml` 3. Prove the absence A recursive grep of the workspace for the secret value, run as a test. Absence is the feature. Files: `test/secrets.sh` ### Done when - [ ] A secret is available as an environment variable in the project - [ ] A recursive grep of the workspace does not find the value - [ ] Listing secrets shows names only - [ ] Removing a secret clears it after a restart ### Watch out - A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. ## Phase 4 · Templates and a new project in one command Starting a project takes seconds rather than an afternoon of setup. ### Steps 1. Build base images per language Runtime, package manager and common tools baked in. Rebuild on a schedule to pick up security updates. Files: `images/` 2. Write the create command Volume, container, route, certificate and starter files in one action, reporting how long it took. Files: `src/create.ts` 3. Make deletion complete Container, volume, route, certificate and secrets. A partial delete leaves a subdomain pointing at nothing and secrets nobody is using. Files: `src/delete.ts` ### Done when - [ ] A new project is ready in under a minute - [ ] The starter project runs with no additional installation - [ ] Deleting removes container, volume, route and secrets - [ ] Base images are rebuilt on a schedule ### Watch out - Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. ## Phase 5 · Deploying what you built A project goes from editor to a public URL with one command. ### Steps 1. Build a separate deployment image From the project source, not from the development container. Different container, different limits, no editor. Files: `src/deploy.ts` 2. Route a public subdomain Separate from the development subdomain, with its own certificate. Files: `Caddyfile` 3. Keep the previous deployment Retain the prior image and a rollback command. Test by deploying something broken. Files: `src/rollback.ts` ### Done when - [ ] A deploy produces a working public URL - [ ] The development editor remains private - [ ] A broken deploy leaves the previous version serving - [ ] The deployed container has no editor or workspace attached ### Watch out - Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. ## Not in this build - The zero-setup experience, since setup is now yours. - Hosted agents that run without your machine. - Multiplayer editing in the browser. - Instant scaling under load. ## After v1, if you want it - Ephemeral preview environments created per branch - A shared base image cache to cut project creation time further - Per-project resource usage reporting, so limits can be set from evidence ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Projects are served at subdomains of this. BASE_DOMAIN=dev.example.com # Required · secret. Needed for wildcard certificate issuance through a DNS challenge. DNS_API_TOKEN=replace-with-provider-token # Required. Per-container memory ceiling. Unset means unlimited, which is how one project takes down the host. PROJECT_MEMORY_LIMIT=1g # Required. Per-container process limit. This is what stops a fork bomb. PROJECT_PIDS_LIMIT=200 # Required · secret. Encrypts the secret store at rest. Losing it loses every project's secrets, so it belongs in your password manager and in the backup. SECRET_STORE_KEY=generate-a-long-random-value # Required. How long a project may sit idle before its container is stopped. IDLE_STOP_MINUTES=60 # Required. Where volumes and the secret store are copied nightly. BACKUP_DEST=s3://your-bucket/dev
You are building a production product version of Replit. 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 ===== # Replit · product brief ## Problem A dev container plus code-server plus deployment can replace some value, but Replit's beginner-friendly cloud IDE, agents, secrets, hosting, and zero-setup environment are a platform. ## Product outcome A browser development environment where projects cannot reach or starve each other, secrets never enter a workspace, deployments are separate from development, idle projects stop costing memory, and a restore brings back both files and secrets. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - cloud VM/container platform - code-server - domain/HTTPS - deploy target - LLM API - backups ## Explicit non-goals for v1 - The zero-setup experience, since setup is now yours. - Hosted agents that run without your machine. - Multiplayer editing in the browser. - Instant scaling under load. - zero-setup browser IDE - hosted agents - multiplayer - templates - deployments - education/community workflows ## Success criteria - Memory exhaustion and fork bomb tests are both contained - A cross-project volume read is confirmed to fail - A workspace grep confirms no secret value is present - A deployed project is confirmed to expose no editor - A restore of volumes and secrets has been performed and dated ===== BRIEF.md ===== # Build brief · Replit 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 a browser development environment on your own server, with isolated projects and one-command deploys to replace Replit. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write a browser IDE. code-server is Visual Studio Code compiled to run on a server and reached through a browser, and it already works. This is an operations task: run it safely, isolate projects from each other, handle secrets, and give yourself a deploy path. Take the isolation seriously. This service exists to run arbitrary code from a browser, which is the same thing an attacker wants. Every phase below treats that as the primary constraint rather than an afterthought. ### Stack (fixed, do not substitute) - Editor: code-server, self-hosted · Visual Studio Code in a browser, with the extension ecosystem already there - Isolation: One container per project with memory and process limits · the difference between one project hanging and the whole server hanging - Routing: Caddy in front, one subdomain per project · automatic certificates and a clean separation between projects - Secrets: Injected as environment variables at container start, never written to disk in the workspace · a secret in a workspace file gets committed, and workspaces are exactly what people commit - Persistence: A volume per project, backed up · the workspace is the user's work and must outlive the container ### Data model (create this before Phase 1) - This is infrastructure, so the state worth writing down is operational. - projects(id, name, subdomain, image, cpu_limit, memory_limit, created_at, last_active_at) - volumes · one per project, holding the workspace, backed up on a schedule - secrets · per project, stored encrypted outside any workspace, injected at container start - templates · base images and starter files per language - sessions · who is authenticated, and to which projects ### Phase 1 · One editor, behind a locked door Build: Provision a server, install Docker, run code-server in a container bound to localhost, and put Caddy in front for a certificate and authentication. Do not expose it directly at any point, not even briefly during setup. Trap: A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. Done when: The editor loads over HTTPS after authentication. An unauthenticated request is refused before reaching code-server. A port scan from another machine shows only SSH and the web ports. The editor's own port is not reachable from outside. Do not build yet: No isolation, no secrets, no deploys. ### Phase 2 · One container per project Build: A container per project with its own volume, memory limit, process limit and no privileged access. Route each project to its own subdomain. Prove the isolation by deliberately trying to exhaust memory and to read another project's files. Trap: A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. Done when: A deliberate memory exhaustion in one project kills that container and leaves other projects and the host responsive. A fork bomb hits the process limit rather than the host. Reading another project's volume from inside a container fails. Each project is reachable at its own subdomain with its own certificate. Do not build yet: No secrets and no deploys yet. ### Phase 3 · Secrets that do not end up in git Build: Store secrets per project outside every workspace, encrypted at rest. Inject them as environment variables when the container starts. Provide a command to set and list them, and make listing show names rather than values. Trap: A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. Done when: A secret is available as an environment variable inside the project and is absent from every file in the workspace, verified by a recursive grep. Listing secrets shows names and not values. Restarting the container re-injects them. A secret removed from the store is gone from the environment after a restart. Do not build yet: No templates or deploys yet. ### Phase 4 · Templates and a new project in one command Build: Base images per language with the runtime and common tooling preinstalled. A command that creates a project from a template: volume, container, subdomain, certificate and starter files. Time it, because the whole value here is that it is fast. Trap: Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. Done when: Creating a project from a template produces a working editor with the runtime available in under a minute. The starter project runs without any additional installation. Deleting a project removes its container, volume, subdomain and secrets together, verified by checking each. Do not build yet: No deploy path yet. ### Phase 5 · Deploying what you built Build: A deploy command that builds the project into an image, runs it as a separate container from the development one, and routes a public subdomain to it. Keep the previous deployment so a rollback is immediate. Trap: Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. Done when: A deploy produces a public URL serving the project, and the development editor stays private. A broken deploy leaves the previous version serving. Rollback restores the previous deployment in under a minute. The deployed container has no editor and no workspace volume attached. Do not build yet: Backups and lifecycle come last. ### Phase 6 · Backups, idle shutdown and the restore drill Build: Back up every project volume and the secret store off the machine. Stop containers that have been idle, and start them again on the next request. Restore a project onto a clean server and confirm the workspace and its secrets both came back. Trap: Backing up volumes without the secret store gives you a restore where every project is present and none of them can reach their APIs, and the secrets exist nowhere else by design. Back up both, and treat the secret store as the more important half. Done when: Project volumes and the secret store both land off the server nightly. An idle project's container stops and restarts on the next request without losing workspace contents. A restore onto a clean server reproduces a project with its files and its secrets, and the date is recorded. Do not build yet: Nothing after this. ### Out of scope (and why) - The zero-setup experience. Someone still has to run this server, patch it and pay for it, and that someone is now you. - Hosted agents that keep working when your browser is closed, which is compute you would be renting anyway. - Multiplayer editing in the browser, which is a substantial engineering effort on top of the editor. - The instant scaling that makes a hosted platform feel free at small sizes and expensive at large ones. ### README must contain - That this service runs arbitrary code and isolation is the primary constraint, not a feature - The per-project limits on memory, processes and files, and how each was tested - That secrets are injected at container start and never written into a workspace - That deployments are separate containers, and the development editor is never public - The backup schedule for volumes and the secret store, and the date of the last restore ===== ARCHITECTURE.md ===== # Architecture · Replit ## Stack | Part | Choice | Why | | --- | --- | --- | | Editor | code-server, self-hosted | Visual Studio Code in a browser, with the extension ecosystem already there | | Isolation | One container per project with memory and process limits | the difference between one project hanging and the whole server hanging | | Routing | Caddy in front, one subdomain per project | automatic certificates and a clean separation between projects | | Secrets | Injected as environment variables at container start, never written to disk in the workspace | a secret in a workspace file gets committed, and workspaces are exactly what people commit | | Persistence | A volume per project, backed up | the workspace is the user's work and must outlive the container | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Editor | The browser development experience. | code-server is the choice. Replacing it means writing a browser IDE, which rule zero exists to prevent. | | Isolation | Per-project containers and limits. | Not swappable for convenience. Every limit here was added because its absence has a specific failure mode. | | Secrets | Encrypted storage and runtime injection. | A managed secret manager is a straight upgrade. The rule that nothing is written into a workspace survives any swap. | | Templates | Base images and project creation. | Images are rebuildable. Baking runtimes in rather than installing on first use is the decision that matters. | | Deploy | Building and serving a project publicly. | Any target. Keeping the development container private is the invariant. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `BASE_DOMAIN` · required · Projects are served at subdomains of this. - `DNS_API_TOKEN` · required, secret · Needed for wildcard certificate issuance through a DNS challenge. - `PROJECT_MEMORY_LIMIT` · required · Per-container memory ceiling. Unset means unlimited, which is how one project takes down the host. - `PROJECT_PIDS_LIMIT` · required · Per-container process limit. This is what stops a fork bomb. - `SECRET_STORE_KEY` · required, secret · Encrypts the secret store at rest. Losing it loses every project's secrets, so it belongs in your password manager and in the backup. - `IDLE_STOP_MINUTES` · required · How long a project may sit idle before its container is stopped. - `BACKUP_DEST` · required · Where volumes and the secret store are copied nightly. ## 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 · Replit product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: code-server, self-hosted, One container per project with memory and process limits, Caddy in front, one subdomain per project, Injected as environment variables at container start, never written to disk in the workspace, A volume per project, backed up. - 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 - A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. - A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. - A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. - Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. - Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. - Backing up volumes without the secret store gives you a restore where every project is present and none of them can reach their APIs, and the secrets exist nowhere else by design. Back up both, and treat the secret store as the more important half. ===== MILESTONES.md ===== # Delivery milestones · Replit Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · One editor, behind a locked door code-server runs and is reachable only after authentication, over TLS. ### Steps 1. Provision and close the server Non-root user, key-only SSH, firewall allowing SSH and the web ports. Verify with a scan from elsewhere. ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable nmap -Pn YOUR_SERVER_IP ``` 2. Run code-server bound to localhost Publish only to 127.0.0.1 so nothing reaches it except through the proxy. ```sh docker run -d -p 127.0.0.1:8080:8080 -v ~/project:/home/coder/project codercom/code-server:latest ``` 3. Put Caddy in front with authentication Certificate and an authentication layer. Test the unauthenticated path before celebrating the authenticated one. Files: `Caddyfile` ### Done when - [ ] The editor loads over HTTPS after authentication - [ ] An unauthenticated request is refused - [ ] The editor port is unreachable from another machine - [ ] Only intended ports are open, verified by a scan ### Watch out - A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. ## M2 · One container per project A runaway process in one project cannot take down the server or reach another project. ### Steps 1. Give every project limits Memory, process count, open files and CPU shares. Defaults are unlimited, which is the problem. ```sh docker run -d --memory=1g --pids-limit=200 --cpus=1 ... ``` 2. Separate volumes and drop privileges One volume per project, no privileged flag, no host mounts, a non-root user inside. Files: `compose/project.yml` 3. Test isolation adversarially Exhaust memory, fork bomb, and attempt to read a sibling project. Each should fail in a contained way. ### Done when - [ ] Memory exhaustion kills only its own container - [ ] A fork bomb hits the process limit rather than the host - [ ] A container cannot read another project's volume - [ ] Each project has its own subdomain and certificate ### Watch out - A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. ## M3 · Secrets that do not end up in git A project can use an API key without that key being anywhere in the workspace. ### Steps 1. Store secrets outside workspaces Encrypted at rest, per project, on the host or in a secret manager. Never inside the volume. Files: `src/secrets.ts` 2. Inject at container start Environment variables passed to the container. Nothing is written into the workspace at any point. Files: `compose/project.yml` 3. Prove the absence A recursive grep of the workspace for the secret value, run as a test. Absence is the feature. Files: `test/secrets.sh` ### Done when - [ ] A secret is available as an environment variable in the project - [ ] A recursive grep of the workspace does not find the value - [ ] Listing secrets shows names only - [ ] Removing a secret clears it after a restart ### Watch out - A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. ## M4 · Templates and a new project in one command Starting a project takes seconds rather than an afternoon of setup. ### Steps 1. Build base images per language Runtime, package manager and common tools baked in. Rebuild on a schedule to pick up security updates. Files: `images/` 2. Write the create command Volume, container, route, certificate and starter files in one action, reporting how long it took. Files: `src/create.ts` 3. Make deletion complete Container, volume, route, certificate and secrets. A partial delete leaves a subdomain pointing at nothing and secrets nobody is using. Files: `src/delete.ts` ### Done when - [ ] A new project is ready in under a minute - [ ] The starter project runs with no additional installation - [ ] Deleting removes container, volume, route and secrets - [ ] Base images are rebuilt on a schedule ### Watch out - Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. ## M5 · Deploying what you built A project goes from editor to a public URL with one command. ### Steps 1. Build a separate deployment image From the project source, not from the development container. Different container, different limits, no editor. Files: `src/deploy.ts` 2. Route a public subdomain Separate from the development subdomain, with its own certificate. Files: `Caddyfile` 3. Keep the previous deployment Retain the prior image and a rollback command. Test by deploying something broken. Files: `src/rollback.ts` ### Done when - [ ] A deploy produces a working public URL - [ ] The development editor remains private - [ ] A broken deploy leaves the previous version serving - [ ] The deployed container has no editor or workspace attached ### Watch out - Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. ## M6 · Backups, idle shutdown and the restore drill (production only) Workspaces survive the server, and idle projects stop consuming it. ### Steps 1. Back up volumes and secrets together Dated, several generations, off the machine. The secret store is encrypted and still belongs off-site. Files: `scripts/backup.sh` 2. Stop idle containers Track last activity and stop after a threshold. Start on the next request, accepting the cold start. Files: `src/idle.ts` 3. Restore onto a clean server Both halves, then open the project and confirm a secret-dependent command works. Record the date. Files: `README.md` ### Done when - [ ] Volumes and the secret store both back up nightly off the server - [ ] An idle container stops and restarts without losing workspace contents - [ ] A restore reproduces files and secrets on a clean server - [ ] The restore date is recorded ### Watch out - Backing up volumes without the secret store gives you a restore where every project is present and none of them can reach their APIs, and the secrets exist nowhere else by design. Back up both, and treat the secret store as the more important half. ===== OPERATIONS.md ===== # Operations · Replit ## Backup Project volumes and the encrypted secret store, nightly, off the machine, several generations. The store's encryption key belongs in a password manager, because a backup you cannot decrypt is not a backup. ## Restore Restore volumes and the store onto a clean server, recreate containers from templates, and confirm a secret-dependent command works inside a project. Record the date. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Host memory and disk, per-container memory against its limit, and idle container count. Containers repeatedly hitting their memory limit indicate the limit is wrong rather than that the project is broken. ## Incident checklist For a compromised account, revoke the session, rotate that project's secrets and inspect the volume before restarting. For host memory pressure, the per-container limits should already have contained it; if they did not, that is the first thing to fix. 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 - [ ] Memory exhaustion and fork bomb tests are both contained - [ ] A cross-project volume read is confirmed to fail - [ ] A workspace grep confirms no secret value is present - [ ] A deployed project is confirmed to expose no editor - [ ] A restore of volumes and secrets has been performed and dated ## Launch constraint Do not market omitted Replit 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. Projects are served at subdomains of this. BASE_DOMAIN=dev.example.com # Required · secret. Needed for wildcard certificate issuance through a DNS challenge. DNS_API_TOKEN=replace-with-provider-token # Required. Per-container memory ceiling. Unset means unlimited, which is how one project takes down the host. PROJECT_MEMORY_LIMIT=1g # Required. Per-container process limit. This is what stops a fork bomb. PROJECT_PIDS_LIMIT=200 # Required · secret. Encrypts the secret store at rest. Losing it loses every project's secrets, so it belongs in your password manager and in the backup. SECRET_STORE_KEY=generate-a-long-random-value # Required. How long a project may sit idle before its container is stopped. IDLE_STOP_MINUTES=60 # Required. Where volumes and the secret store are copied nightly. BACKUP_DEST=s3://your-bucket/dev
# Replit · indie build code-server behind authentication and TLS, one container per project so a runaway process cannot take the machine down, secrets injected at runtime rather than written into workspaces, a template system for new projects, and a deploy command that publishes a project to a real URL. 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 | | --- | --- | --- | | Editor | code-server, self-hosted | Visual Studio Code in a browser, with the extension ecosystem already there | | Isolation | One container per project with memory and process limits | the difference between one project hanging and the whole server hanging | | Routing | Caddy in front, one subdomain per project | automatic certificates and a clean separation between projects | | Secrets | Injected as environment variables at container start, never written to disk in the workspace | a secret in a workspace file gets committed, and workspaces are exactly what people commit | | Persistence | A volume per project, backed up | the workspace is the user's work and must outlive the container | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Linux server with room for several containers** · 10 to 40 USD a month depending on how many projects run at once - Why: Each project is a container with its own memory allowance, so capacity is the number of projects multiplied by their limits. - Get it: Any virtual server provider. Start with something modest and size up once you know how many projects you actually keep. - Verify: free -m shows enough memory for your intended project count and limits - [ ] **Docker Engine with Compose v2** · free - Why: Isolation, limits and lifecycle all come from the container runtime. - Get it: Install from Docker's own repository rather than a distribution package, which is often behind. - Verify: docker compose version reports v2 or higher - [ ] **A domain with a wildcard DNS record** · roughly 10 to 15 USD a year - Why: Each project gets a subdomain, and a wildcard certificate needs a DNS challenge with a provider API token. - Get it: Any registrar with DNS API access. Add a wildcard record and create a scoped token. - Verify: A wildcard hostname resolves and the token can create a TXT record - [ ] **An honest assessment of who can reach this** · free - Why: A browser development environment is a remote shell. Deciding whether it is only you, a small trusted group, or anyone changes every isolation decision. - Get it: Write down who will have accounts and what happens if one is compromised. If the answer is anything beyond a small trusted group, revisit the isolation model before Phase 2. - Verify: The access decision is written down before Phase 1 is deployed - [ ] **An off-server backup destination** · a few USD a month - Why: Workspaces are people's work and the secret store exists nowhere else. Both die with the server otherwise. - Get it: Object storage with credentials scoped to one bucket. - Verify: A test file uploads and lists from your laptop ## Quick start ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable nmap -Pn YOUR_SERVER_IP ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The zero-setup experience, since setup is now yours. - Hosted agents that run without your machine. - Multiplayer editing in the browser. - Instant scaling under load. - zero-setup browser IDE - hosted agents - multiplayer - templates - deployments - education/community workflows If one of those is essential to you, that is the reason to keep paying for Replit, and the README should say so rather than pretend.
# Build brief · Replit 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 a browser development environment on your own server, with isolated projects and one-command deploys to replace Replit. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write a browser IDE. code-server is Visual Studio Code compiled to run on a server and reached through a browser, and it already works. This is an operations task: run it safely, isolate projects from each other, handle secrets, and give yourself a deploy path. Take the isolation seriously. This service exists to run arbitrary code from a browser, which is the same thing an attacker wants. Every phase below treats that as the primary constraint rather than an afterthought. ### Stack (fixed, do not substitute) - Editor: code-server, self-hosted · Visual Studio Code in a browser, with the extension ecosystem already there - Isolation: One container per project with memory and process limits · the difference between one project hanging and the whole server hanging - Routing: Caddy in front, one subdomain per project · automatic certificates and a clean separation between projects - Secrets: Injected as environment variables at container start, never written to disk in the workspace · a secret in a workspace file gets committed, and workspaces are exactly what people commit - Persistence: A volume per project, backed up · the workspace is the user's work and must outlive the container ### Data model (create this before Phase 1) - This is infrastructure, so the state worth writing down is operational. - projects(id, name, subdomain, image, cpu_limit, memory_limit, created_at, last_active_at) - volumes · one per project, holding the workspace, backed up on a schedule - secrets · per project, stored encrypted outside any workspace, injected at container start - templates · base images and starter files per language - sessions · who is authenticated, and to which projects ### Phase 1 · One editor, behind a locked door Build: Provision a server, install Docker, run code-server in a container bound to localhost, and put Caddy in front for a certificate and authentication. Do not expose it directly at any point, not even briefly during setup. Trap: A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. Done when: The editor loads over HTTPS after authentication. An unauthenticated request is refused before reaching code-server. A port scan from another machine shows only SSH and the web ports. The editor's own port is not reachable from outside. Do not build yet: No isolation, no secrets, no deploys. ### Phase 2 · One container per project Build: A container per project with its own volume, memory limit, process limit and no privileged access. Route each project to its own subdomain. Prove the isolation by deliberately trying to exhaust memory and to read another project's files. Trap: A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. Done when: A deliberate memory exhaustion in one project kills that container and leaves other projects and the host responsive. A fork bomb hits the process limit rather than the host. Reading another project's volume from inside a container fails. Each project is reachable at its own subdomain with its own certificate. Do not build yet: No secrets and no deploys yet. ### Phase 3 · Secrets that do not end up in git Build: Store secrets per project outside every workspace, encrypted at rest. Inject them as environment variables when the container starts. Provide a command to set and list them, and make listing show names rather than values. Trap: A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. Done when: A secret is available as an environment variable inside the project and is absent from every file in the workspace, verified by a recursive grep. Listing secrets shows names and not values. Restarting the container re-injects them. A secret removed from the store is gone from the environment after a restart. Do not build yet: No templates or deploys yet. ### Phase 4 · Templates and a new project in one command Build: Base images per language with the runtime and common tooling preinstalled. A command that creates a project from a template: volume, container, subdomain, certificate and starter files. Time it, because the whole value here is that it is fast. Trap: Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. Done when: Creating a project from a template produces a working editor with the runtime available in under a minute. The starter project runs without any additional installation. Deleting a project removes its container, volume, subdomain and secrets together, verified by checking each. Do not build yet: No deploy path yet. ### Phase 5 · Deploying what you built Build: A deploy command that builds the project into an image, runs it as a separate container from the development one, and routes a public subdomain to it. Keep the previous deployment so a rollback is immediate. Trap: Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. Done when: A deploy produces a public URL serving the project, and the development editor stays private. A broken deploy leaves the previous version serving. Rollback restores the previous deployment in under a minute. The deployed container has no editor and no workspace volume attached. Do not build yet: Backups and lifecycle come last. ### Phase 6 · Backups, idle shutdown and the restore drill Build: Back up every project volume and the secret store off the machine. Stop containers that have been idle, and start them again on the next request. Restore a project onto a clean server and confirm the workspace and its secrets both came back. Trap: Backing up volumes without the secret store gives you a restore where every project is present and none of them can reach their APIs, and the secrets exist nowhere else by design. Back up both, and treat the secret store as the more important half. Done when: Project volumes and the secret store both land off the server nightly. An idle project's container stops and restarts on the next request without losing workspace contents. A restore onto a clean server reproduces a project with its files and its secrets, and the date is recorded. Do not build yet: Nothing after this. ### Out of scope (and why) - The zero-setup experience. Someone still has to run this server, patch it and pay for it, and that someone is now you. - Hosted agents that keep working when your browser is closed, which is compute you would be renting anyway. - Multiplayer editing in the browser, which is a substantial engineering effort on top of the editor. - The instant scaling that makes a hosted platform feel free at small sizes and expensive at large ones. ### README must contain - That this service runs arbitrary code and isolation is the primary constraint, not a feature - The per-project limits on memory, processes and files, and how each was tested - That secrets are injected at container start and never written into a workspace - That deployments are separate containers, and the development editor is never public - The backup schedule for volumes and the secret store, and the date of the last restore
# Agent instructions · Replit indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: code-server, self-hosted, One container per project with memory and process limits, Caddy in front, one subdomain per project, Injected as environment variables at container start, never written to disk in the workspace, A volume per project, backed up. 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 - A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. - A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. - A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. - Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. - Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private.
# Build plan · Replit code-server behind authentication and TLS, one container per project so a runaway process cannot take the machine down, secrets injected at runtime rather than written into workspaces, a template system for new projects, and a deploy command that publishes a project to a real URL. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · One editor, behind a locked door code-server runs and is reachable only after authentication, over TLS. ### Steps 1. Provision and close the server Non-root user, key-only SSH, firewall allowing SSH and the web ports. Verify with a scan from elsewhere. ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable nmap -Pn YOUR_SERVER_IP ``` 2. Run code-server bound to localhost Publish only to 127.0.0.1 so nothing reaches it except through the proxy. ```sh docker run -d -p 127.0.0.1:8080:8080 -v ~/project:/home/coder/project codercom/code-server:latest ``` 3. Put Caddy in front with authentication Certificate and an authentication layer. Test the unauthenticated path before celebrating the authenticated one. Files: `Caddyfile` ### Done when - [ ] The editor loads over HTTPS after authentication - [ ] An unauthenticated request is refused - [ ] The editor port is unreachable from another machine - [ ] Only intended ports are open, verified by a scan ### Watch out - A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. ## Phase 2 · One container per project A runaway process in one project cannot take down the server or reach another project. ### Steps 1. Give every project limits Memory, process count, open files and CPU shares. Defaults are unlimited, which is the problem. ```sh docker run -d --memory=1g --pids-limit=200 --cpus=1 ... ``` 2. Separate volumes and drop privileges One volume per project, no privileged flag, no host mounts, a non-root user inside. Files: `compose/project.yml` 3. Test isolation adversarially Exhaust memory, fork bomb, and attempt to read a sibling project. Each should fail in a contained way. ### Done when - [ ] Memory exhaustion kills only its own container - [ ] A fork bomb hits the process limit rather than the host - [ ] A container cannot read another project's volume - [ ] Each project has its own subdomain and certificate ### Watch out - A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. ## Phase 3 · Secrets that do not end up in git A project can use an API key without that key being anywhere in the workspace. ### Steps 1. Store secrets outside workspaces Encrypted at rest, per project, on the host or in a secret manager. Never inside the volume. Files: `src/secrets.ts` 2. Inject at container start Environment variables passed to the container. Nothing is written into the workspace at any point. Files: `compose/project.yml` 3. Prove the absence A recursive grep of the workspace for the secret value, run as a test. Absence is the feature. Files: `test/secrets.sh` ### Done when - [ ] A secret is available as an environment variable in the project - [ ] A recursive grep of the workspace does not find the value - [ ] Listing secrets shows names only - [ ] Removing a secret clears it after a restart ### Watch out - A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. ## Phase 4 · Templates and a new project in one command Starting a project takes seconds rather than an afternoon of setup. ### Steps 1. Build base images per language Runtime, package manager and common tools baked in. Rebuild on a schedule to pick up security updates. Files: `images/` 2. Write the create command Volume, container, route, certificate and starter files in one action, reporting how long it took. Files: `src/create.ts` 3. Make deletion complete Container, volume, route, certificate and secrets. A partial delete leaves a subdomain pointing at nothing and secrets nobody is using. Files: `src/delete.ts` ### Done when - [ ] A new project is ready in under a minute - [ ] The starter project runs with no additional installation - [ ] Deleting removes container, volume, route and secrets - [ ] Base images are rebuilt on a schedule ### Watch out - Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. ## Phase 5 · Deploying what you built A project goes from editor to a public URL with one command. ### Steps 1. Build a separate deployment image From the project source, not from the development container. Different container, different limits, no editor. Files: `src/deploy.ts` 2. Route a public subdomain Separate from the development subdomain, with its own certificate. Files: `Caddyfile` 3. Keep the previous deployment Retain the prior image and a rollback command. Test by deploying something broken. Files: `src/rollback.ts` ### Done when - [ ] A deploy produces a working public URL - [ ] The development editor remains private - [ ] A broken deploy leaves the previous version serving - [ ] The deployed container has no editor or workspace attached ### Watch out - Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. ## Not in this build - The zero-setup experience, since setup is now yours. - Hosted agents that run without your machine. - Multiplayer editing in the browser. - Instant scaling under load. ## After v1, if you want it - Ephemeral preview environments created per branch - A shared base image cache to cut project creation time further - Per-project resource usage reporting, so limits can be set from evidence
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Projects are served at subdomains of this. BASE_DOMAIN=dev.example.com # Required · secret. Needed for wildcard certificate issuance through a DNS challenge. DNS_API_TOKEN=replace-with-provider-token # Required. Per-container memory ceiling. Unset means unlimited, which is how one project takes down the host. PROJECT_MEMORY_LIMIT=1g # Required. Per-container process limit. This is what stops a fork bomb. PROJECT_PIDS_LIMIT=200 # Required · secret. Encrypts the secret store at rest. Losing it loses every project's secrets, so it belongs in your password manager and in the backup. SECRET_STORE_KEY=generate-a-long-random-value # Required. How long a project may sit idle before its container is stopped. IDLE_STOP_MINUTES=60 # Required. Where volumes and the secret store are copied nightly. BACKUP_DEST=s3://your-bucket/dev
# Replit · product brief ## Problem A dev container plus code-server plus deployment can replace some value, but Replit's beginner-friendly cloud IDE, agents, secrets, hosting, and zero-setup environment are a platform. ## Product outcome A browser development environment where projects cannot reach or starve each other, secrets never enter a workspace, deployments are separate from development, idle projects stop costing memory, and a restore brings back both files and secrets. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - cloud VM/container platform - code-server - domain/HTTPS - deploy target - LLM API - backups ## Explicit non-goals for v1 - The zero-setup experience, since setup is now yours. - Hosted agents that run without your machine. - Multiplayer editing in the browser. - Instant scaling under load. - zero-setup browser IDE - hosted agents - multiplayer - templates - deployments - education/community workflows ## Success criteria - Memory exhaustion and fork bomb tests are both contained - A cross-project volume read is confirmed to fail - A workspace grep confirms no secret value is present - A deployed project is confirmed to expose no editor - A restore of volumes and secrets has been performed and dated
# Build brief · Replit 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 a browser development environment on your own server, with isolated projects and one-command deploys to replace Replit. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write a browser IDE. code-server is Visual Studio Code compiled to run on a server and reached through a browser, and it already works. This is an operations task: run it safely, isolate projects from each other, handle secrets, and give yourself a deploy path. Take the isolation seriously. This service exists to run arbitrary code from a browser, which is the same thing an attacker wants. Every phase below treats that as the primary constraint rather than an afterthought. ### Stack (fixed, do not substitute) - Editor: code-server, self-hosted · Visual Studio Code in a browser, with the extension ecosystem already there - Isolation: One container per project with memory and process limits · the difference between one project hanging and the whole server hanging - Routing: Caddy in front, one subdomain per project · automatic certificates and a clean separation between projects - Secrets: Injected as environment variables at container start, never written to disk in the workspace · a secret in a workspace file gets committed, and workspaces are exactly what people commit - Persistence: A volume per project, backed up · the workspace is the user's work and must outlive the container ### Data model (create this before Phase 1) - This is infrastructure, so the state worth writing down is operational. - projects(id, name, subdomain, image, cpu_limit, memory_limit, created_at, last_active_at) - volumes · one per project, holding the workspace, backed up on a schedule - secrets · per project, stored encrypted outside any workspace, injected at container start - templates · base images and starter files per language - sessions · who is authenticated, and to which projects ### Phase 1 · One editor, behind a locked door Build: Provision a server, install Docker, run code-server in a container bound to localhost, and put Caddy in front for a certificate and authentication. Do not expose it directly at any point, not even briefly during setup. Trap: A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. Done when: The editor loads over HTTPS after authentication. An unauthenticated request is refused before reaching code-server. A port scan from another machine shows only SSH and the web ports. The editor's own port is not reachable from outside. Do not build yet: No isolation, no secrets, no deploys. ### Phase 2 · One container per project Build: A container per project with its own volume, memory limit, process limit and no privileged access. Route each project to its own subdomain. Prove the isolation by deliberately trying to exhaust memory and to read another project's files. Trap: A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. Done when: A deliberate memory exhaustion in one project kills that container and leaves other projects and the host responsive. A fork bomb hits the process limit rather than the host. Reading another project's volume from inside a container fails. Each project is reachable at its own subdomain with its own certificate. Do not build yet: No secrets and no deploys yet. ### Phase 3 · Secrets that do not end up in git Build: Store secrets per project outside every workspace, encrypted at rest. Inject them as environment variables when the container starts. Provide a command to set and list them, and make listing show names rather than values. Trap: A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. Done when: A secret is available as an environment variable inside the project and is absent from every file in the workspace, verified by a recursive grep. Listing secrets shows names and not values. Restarting the container re-injects them. A secret removed from the store is gone from the environment after a restart. Do not build yet: No templates or deploys yet. ### Phase 4 · Templates and a new project in one command Build: Base images per language with the runtime and common tooling preinstalled. A command that creates a project from a template: volume, container, subdomain, certificate and starter files. Time it, because the whole value here is that it is fast. Trap: Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. Done when: Creating a project from a template produces a working editor with the runtime available in under a minute. The starter project runs without any additional installation. Deleting a project removes its container, volume, subdomain and secrets together, verified by checking each. Do not build yet: No deploy path yet. ### Phase 5 · Deploying what you built Build: A deploy command that builds the project into an image, runs it as a separate container from the development one, and routes a public subdomain to it. Keep the previous deployment so a rollback is immediate. Trap: Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. Done when: A deploy produces a public URL serving the project, and the development editor stays private. A broken deploy leaves the previous version serving. Rollback restores the previous deployment in under a minute. The deployed container has no editor and no workspace volume attached. Do not build yet: Backups and lifecycle come last. ### Phase 6 · Backups, idle shutdown and the restore drill Build: Back up every project volume and the secret store off the machine. Stop containers that have been idle, and start them again on the next request. Restore a project onto a clean server and confirm the workspace and its secrets both came back. Trap: Backing up volumes without the secret store gives you a restore where every project is present and none of them can reach their APIs, and the secrets exist nowhere else by design. Back up both, and treat the secret store as the more important half. Done when: Project volumes and the secret store both land off the server nightly. An idle project's container stops and restarts on the next request without losing workspace contents. A restore onto a clean server reproduces a project with its files and its secrets, and the date is recorded. Do not build yet: Nothing after this. ### Out of scope (and why) - The zero-setup experience. Someone still has to run this server, patch it and pay for it, and that someone is now you. - Hosted agents that keep working when your browser is closed, which is compute you would be renting anyway. - Multiplayer editing in the browser, which is a substantial engineering effort on top of the editor. - The instant scaling that makes a hosted platform feel free at small sizes and expensive at large ones. ### README must contain - That this service runs arbitrary code and isolation is the primary constraint, not a feature - The per-project limits on memory, processes and files, and how each was tested - That secrets are injected at container start and never written into a workspace - That deployments are separate containers, and the development editor is never public - The backup schedule for volumes and the secret store, and the date of the last restore
# Architecture · Replit ## Stack | Part | Choice | Why | | --- | --- | --- | | Editor | code-server, self-hosted | Visual Studio Code in a browser, with the extension ecosystem already there | | Isolation | One container per project with memory and process limits | the difference between one project hanging and the whole server hanging | | Routing | Caddy in front, one subdomain per project | automatic certificates and a clean separation between projects | | Secrets | Injected as environment variables at container start, never written to disk in the workspace | a secret in a workspace file gets committed, and workspaces are exactly what people commit | | Persistence | A volume per project, backed up | the workspace is the user's work and must outlive the container | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Editor | The browser development experience. | code-server is the choice. Replacing it means writing a browser IDE, which rule zero exists to prevent. | | Isolation | Per-project containers and limits. | Not swappable for convenience. Every limit here was added because its absence has a specific failure mode. | | Secrets | Encrypted storage and runtime injection. | A managed secret manager is a straight upgrade. The rule that nothing is written into a workspace survives any swap. | | Templates | Base images and project creation. | Images are rebuildable. Baking runtimes in rather than installing on first use is the decision that matters. | | Deploy | Building and serving a project publicly. | Any target. Keeping the development container private is the invariant. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `BASE_DOMAIN` · required · Projects are served at subdomains of this. - `DNS_API_TOKEN` · required, secret · Needed for wildcard certificate issuance through a DNS challenge. - `PROJECT_MEMORY_LIMIT` · required · Per-container memory ceiling. Unset means unlimited, which is how one project takes down the host. - `PROJECT_PIDS_LIMIT` · required · Per-container process limit. This is what stops a fork bomb. - `SECRET_STORE_KEY` · required, secret · Encrypts the secret store at rest. Losing it loses every project's secrets, so it belongs in your password manager and in the backup. - `IDLE_STOP_MINUTES` · required · How long a project may sit idle before its container is stopped. - `BACKUP_DEST` · required · Where volumes and the secret store are copied nightly. ## 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 · Replit product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: code-server, self-hosted, One container per project with memory and process limits, Caddy in front, one subdomain per project, Injected as environment variables at container start, never written to disk in the workspace, A volume per project, backed up. - 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 - A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. - A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. - A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. - Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. - Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. - Backing up volumes without the secret store gives you a restore where every project is present and none of them can reach their APIs, and the secrets exist nowhere else by design. Back up both, and treat the secret store as the more important half.
# Delivery milestones · Replit Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · One editor, behind a locked door code-server runs and is reachable only after authentication, over TLS. ### Steps 1. Provision and close the server Non-root user, key-only SSH, firewall allowing SSH and the web ports. Verify with a scan from elsewhere. ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable nmap -Pn YOUR_SERVER_IP ``` 2. Run code-server bound to localhost Publish only to 127.0.0.1 so nothing reaches it except through the proxy. ```sh docker run -d -p 127.0.0.1:8080:8080 -v ~/project:/home/coder/project codercom/code-server:latest ``` 3. Put Caddy in front with authentication Certificate and an authentication layer. Test the unauthenticated path before celebrating the authenticated one. Files: `Caddyfile` ### Done when - [ ] The editor loads over HTTPS after authentication - [ ] An unauthenticated request is refused - [ ] The editor port is unreachable from another machine - [ ] Only intended ports are open, verified by a scan ### Watch out - A code-server instance reachable without authentication is a remote shell on your server offered to the internet, and these get found by scanners within hours rather than days. Put authentication in front before the port is ever reachable, not after you have confirmed the editor works. ## M2 · One container per project A runaway process in one project cannot take down the server or reach another project. ### Steps 1. Give every project limits Memory, process count, open files and CPU shares. Defaults are unlimited, which is the problem. ```sh docker run -d --memory=1g --pids-limit=200 --cpus=1 ... ``` 2. Separate volumes and drop privileges One volume per project, no privileged flag, no host mounts, a non-root user inside. Files: `compose/project.yml` 3. Test isolation adversarially Exhaust memory, fork bomb, and attempt to read a sibling project. Each should fail in a contained way. ### Done when - [ ] Memory exhaustion kills only its own container - [ ] A fork bomb hits the process limit rather than the host - [ ] A container cannot read another project's volume - [ ] Each project has its own subdomain and certificate ### Watch out - A container without a memory limit will happily consume the host's memory until the kernel starts killing processes, and the process it kills may well be something else important. Set memory, process count and open file limits explicitly, then test each one by exceeding it on purpose. ## M3 · Secrets that do not end up in git A project can use an API key without that key being anywhere in the workspace. ### Steps 1. Store secrets outside workspaces Encrypted at rest, per project, on the host or in a secret manager. Never inside the volume. Files: `src/secrets.ts` 2. Inject at container start Environment variables passed to the container. Nothing is written into the workspace at any point. Files: `compose/project.yml` 3. Prove the absence A recursive grep of the workspace for the secret value, run as a test. Absence is the feature. Files: `test/secrets.sh` ### Done when - [ ] A secret is available as an environment variable in the project - [ ] A recursive grep of the workspace does not find the value - [ ] Listing secrets shows names only - [ ] Removing a secret clears it after a restart ### Watch out - A secret written into a .env file inside the workspace is inside a git repository the moment the user runs git init, and workspaces are exactly what people push. Inject at container start so the value exists in the process environment and never in a file the user can commit. ## M4 · Templates and a new project in one command Starting a project takes seconds rather than an afternoon of setup. ### Steps 1. Build base images per language Runtime, package manager and common tools baked in. Rebuild on a schedule to pick up security updates. Files: `images/` 2. Write the create command Volume, container, route, certificate and starter files in one action, reporting how long it took. Files: `src/create.ts` 3. Make deletion complete Container, volume, route, certificate and secrets. A partial delete leaves a subdomain pointing at nothing and secrets nobody is using. Files: `src/delete.ts` ### Done when - [ ] A new project is ready in under a minute - [ ] The starter project runs with no additional installation - [ ] Deleting removes container, volume, route and secrets - [ ] Base images are rebuilt on a schedule ### Watch out - Installing a language runtime on first use inside a generic container means every new project starts with several minutes of downloading. Bake the runtime into the image so a new project is ready in seconds, and rebuild images on a schedule rather than at project creation. ## M5 · Deploying what you built A project goes from editor to a public URL with one command. ### Steps 1. Build a separate deployment image From the project source, not from the development container. Different container, different limits, no editor. Files: `src/deploy.ts` 2. Route a public subdomain Separate from the development subdomain, with its own certificate. Files: `Caddyfile` 3. Keep the previous deployment Retain the prior image and a rollback command. Test by deploying something broken. Files: `src/rollback.ts` ### Done when - [ ] A deploy produces a working public URL - [ ] The development editor remains private - [ ] A broken deploy leaves the previous version serving - [ ] The deployed container has no editor or workspace attached ### Watch out - Serving the development container publicly means the editor, the workspace and any process the user started are all exposed on the public URL. Deploy a separate container built from the project, and keep the development environment private. ## M6 · Backups, idle shutdown and the restore drill (production only) Workspaces survive the server, and idle projects stop consuming it. ### Steps 1. Back up volumes and secrets together Dated, several generations, off the machine. The secret store is encrypted and still belongs off-site. Files: `scripts/backup.sh` 2. Stop idle containers Track last activity and stop after a threshold. Start on the next request, accepting the cold start. Files: `src/idle.ts` 3. Restore onto a clean server Both halves, then open the project and confirm a secret-dependent command works. Record the date. Files: `README.md` ### Done when - [ ] Volumes and the secret store both back up nightly off the server - [ ] An idle container stops and restarts without losing workspace contents - [ ] A restore reproduces files and secrets on a clean server - [ ] The restore date is recorded ### Watch out - Backing up volumes without the secret store gives you a restore where every project is present and none of them can reach their APIs, and the secrets exist nowhere else by design. Back up both, and treat the secret store as the more important half.
# Operations · Replit ## Backup Project volumes and the encrypted secret store, nightly, off the machine, several generations. The store's encryption key belongs in a password manager, because a backup you cannot decrypt is not a backup. ## Restore Restore volumes and the store onto a clean server, recreate containers from templates, and confirm a secret-dependent command works inside a project. Record the date. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Host memory and disk, per-container memory against its limit, and idle container count. Containers repeatedly hitting their memory limit indicate the limit is wrong rather than that the project is broken. ## Incident checklist For a compromised account, revoke the session, rotate that project's secrets and inspect the volume before restarting. For host memory pressure, the per-container limits should already have contained it; if they did not, that is the first thing to fix. 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 - [ ] Memory exhaustion and fork bomb tests are both contained - [ ] A cross-project volume read is confirmed to fail - [ ] A workspace grep confirms no secret value is present - [ ] A deployed project is confirmed to expose no editor - [ ] A restore of volumes and secrets has been performed and dated ## Launch constraint Do not market omitted Replit 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. Projects are served at subdomains of this. BASE_DOMAIN=dev.example.com # Required · secret. Needed for wildcard certificate issuance through a DNS challenge. DNS_API_TOKEN=replace-with-provider-token # Required. Per-container memory ceiling. Unset means unlimited, which is how one project takes down the host. PROJECT_MEMORY_LIMIT=1g # Required. Per-container process limit. This is what stops a fork bomb. PROJECT_PIDS_LIMIT=200 # Required · secret. Encrypts the secret store at rest. Losing it loses every project's secrets, so it belongs in your password manager and in the backup. SECRET_STORE_KEY=generate-a-long-random-value # Required. How long a project may sit idle before its container is stopped. IDLE_STOP_MINUTES=60 # Required. Where volumes and the secret store are copied nightly. BACKUP_DEST=s3://your-bucket/dev
$ choose a build depth, inspect the files, then open the complete pack in your agent
They pay because code runs in the browser immediately without local environment drama.
xzero-setup browser IDE
xhosted agents
xmultiplayer
xtemplates
xdeployments
xeducation/community workflows
Don't feel like building it? These folks already made it free.
all 3 free alternatives to Replit →· no votes, no pay-to-list · just what's real
Replit pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| starter | $0/user | $0/user | 1 published project; free daily Agent credits are provided, but the numeric daily amount is not publicly stated. |
| core | $25/user | $20/user | $25 in monthly usage credits; 5 collaborators; 2 parallel agents. |
| pro | $100/user | $95/user | $100 in monthly usage credits; 15 collaborators; 50 viewers; 10 parallel agents; 28-day rollback history. |
| enterprise | custom | — | Custom seats, credits, controls, support and contract terms. |
free tier1 published project plus a daily Agent-credit grant whose exact numeric size is not publicly stated
billingmonthly and annual for Core and Pro; Enterprise custom
hidden costsAgent work, deployments, compute, storage and other resources draw from the included dollar credit and then become pay as you go; current resource rates can change, and taxes are extra.
verified 2026-08-14 · source ↗
Vibecode Replit
Kinda. The core of Replit is buildable in a weekend with the prompt on this page, but there are real gaps: zero-setup browser IDE, hosted agents. Read the honest list above before committing.
How much does Replit cost?
Replit costs about $25/month (Core, checked 2026-07-30), which is $300 per year.
What do I lose by replacing Replit?
Honestly: zero-setup browser IDE; hosted agents; multiplayer; templates; deployments; education/community workflows. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Replit?
Yes: code-server (VS Code in a browser; agents, hosting and deployment are separate chores.) Coder (Full cloud workspaces on your infrastructure; Terraform and PostgreSQL come along for the ride.) Eclipse Che (A real browser IDE platform, assuming Kubernetes already counts as normal in your house.) All 3 curated free alternatives are at vibecodeit.com/replit/alternatives. The prompt is for when you want it exactly your way.