Why can’t the new developer run it? When someone cannot run the project locally, check the setup before the code: it often lives in one person’s head and one laptop’s state. The fix is 4 things in the repository: one documented command, an env example, pinned versions and seed data, proven by a fresh clone on a clean machine.
Cannot run the project locally: the four things the repository is missing
A project a new developer cannot run locally is usually missing 4 things from its repository: one documented setup command, an env example that names every variable, pinned runtime and dependency versions, and seed data. With all four in place, a stranger can go from clone to a logged-in running app by following the guide alone.
Local setup is one of the engineering standards for AI-assisted teams, and the easiest one to prove. It also sits inside the wider documentation for a vibe-coded app, whose setup section is a short list; this page is that section written out in full.
| The thing | The file it lives in | What it does | The first-hour failure it prevents |
|---|---|---|---|
| One setup command | make setup, npm run setup or bin/setup | Installs, configures, starts local services, migrates and seeds, then checks the app answers | The newcomer piecing together a dozen commands from chat history |
| An env example | .env.example, committed | Names every variable, with a safe placeholder and a comment saying where the real value comes from | The app starts and every page errors on a missing variable |
| Pinned versions | A runtime version file and a committed lockfile | Fixes the language version and the exact dependency tree | An install that fails, or an app that behaves differently on the second laptop |
| Seed data | A seed script | Fills an empty local database with fixture logins and realistic rows | A login screen nobody can get past, and empty pages that hide broken queries |
What belongs in that file, variable by variable, is covered in what goes in an env example file, and the seed is a separate job: how to generate realistic fake data for staging. The fifth piece is the guide itself: a “Run it locally” section of the README with the prerequisites, the one command, the local URL and the fixture login. Everything else a README carries is a wider question: what belongs in a README.
My working rule for the target: someone who has never seen the project should get from clone to a logged-in running app in under an hour, with no help. Treat that as a target to aim at, not a measured norm.
In the Production Hardening Sprint, this is deliverable 10.10: provide one documented command that brings the application up locally, with an environment example and seed data. The reason on the published list is that the next engineer’s first hour decides whether they can work on the product at all, and generated apps rarely have this.
What goes wrong without it: the app works on one machine only
An app that works on one machine only fails for a newcomer in a predictable set of ways. The causes below are the typical ones, in my reading, and each maps to a file that would have prevented it.
| What the new developer sees | The typical cause | The file that prevents it |
|---|---|---|
| Install fails, or the app crashes on start with a syntax or module error | The runtime version differs from the author’s, and nothing records it | A runtime version file |
| The app starts and every page errors | A variable is missing: there is no env example, or it is out of date | .env.example |
| It runs, but behaves differently | No committed lockfile, or an install that rewrote it | A committed lockfile and a frozen install |
relation "…" does not exist, or an empty screen | No migrations ran and nothing seeded the database | The migrate and seed steps of the setup command |
| The shell reports a command not found | A global tool the author installed long ago: the database CLI, the payment provider’s CLI, the package manager | The prerequisites list in the guide |
| It fails only on the other operating system | Case-sensitive file names, a native module, a different CPU architecture | A dev container, or a CI run on the other system |
| It has never run outside the AI builder | A Lovable, Bolt or Replit project whose preview was the only runtime | The whole set: repository, env example, command |
The lockfile row has a mirror image when the app runs on a laptop and fails on the server; the install-command side of that is explained in why an app works locally but not in production. The last row is a bigger job than a setup script, and what running without the builder actually means lists what it takes. For an agency, in my reading, any of these rows is the handover failing in front of the client.
If your search for this problem turned up IntelliJ, Eclipse or Visual Studio threads about a greyed-out run button, those are project-import problems inside the IDE, a separate subject from a repository that cannot start.
How to do it on the common stacks
Five parts follow: the command, pinned versions, the Python tooling questions, tools that pin the whole environment, and the first day for the next engineer.
One command: what the setup script does, in order
A setup command does 8 things in order: checks the runtime version, installs exactly what the lockfile records, creates the env file from the example, starts local services, runs migrations, seeds the database, runs a smoke check, and prints the URL and a fixture login. Running it twice is safe.
This is my working order, step by step.
- 01 Check the runtime version against the version file, and stop with a clear message if it differs.
- 02 Install exactly what the lockfile records:
npm cirather thannpm install,pnpm install --frozen-lockfile,poetry installfrompoetry.lock, oruv sync --locked. - 03 Create the local env file from the example if none exists. Never overwrite one that does.
- 04 Start local services: a database and anything else in a compose file, or the platform local stack, such as
supabase start. - 05 Run the migrations.
- 06 Run the seed.
- 07 Run a smoke check: the health route answers.
- 08 Print the local URL and the fixture login.
The install step is where the lockfile earns its place. With --frozen-lockfile, pnpm install does not update pnpm-lock.yaml and fails if the lockfile is out of sync with the manifest; poetry install uses the exact versions from poetry.lock when one is present, instead of resolving them; and uv sync --locked exits with an error if the lockfile is missing or needs to be updated. For a Supabase project, supabase start needs the Supabase CLI and a container manager compatible with Docker APIs, per Supabase’s local development guide. The rest of the local Supabase loop, including whether you need a local Supabase environment too, has its own article.
The rules around the steps are my working rules too. The script is idempotent, needs no secret a new person does not have, and never touches a remote database. Third-party services run on test keys or a local stub, and the env example lists which. It is a shell script, a Makefile target or a package script, whichever the team already reads. A Node.js version with a compose file fits in twelve lines:
#!/usr/bin/env bash
# bin/setup: safe to run twice, no production secret, never a remote database
set -euo pipefail
want="$(sed 's/^v//' .nvmrc)"; have="$(node -p 'process.versions.node')"
[ "$have" = "$want" ] || { echo "Need Node $want (see .nvmrc), found $have"; exit 1; }
npm ci # exactly what package-lock.json records
[ -f .env ] || cp .env.example .env # create the env file, never overwrite it
docker compose up --wait db # waits until db is running|healthy
npm run db:migrate # the project's own migration script
npm run db:seed # fixture logins and realistic rows
npm run smoke # project script: starts the app, checks the health route
grep -E '^(APP_URL|FIXTURE_EMAIL|FIXTURE_PASSWORD)=' .env
The .nvmrc here holds an exact version, so the comparison is exact. Running the script a second time leaves the env file alone, and docker compose up only recreates a container whose configuration or image changed.
Picture the case this protects against. A commit adds a dependency to package.json and leaves the committed lockfile as it was. The new developer’s setup script runs npm ci, which, per the npm ci docs, exits with an error when the dependencies in the package lock do not match those in package.json, instead of updating the lock. The fix is to regenerate the lockfile once with npm install, commit it, and keep npm ci in the script: a frozen install that fails on the first morning is the setup doing its job.
Works on my machine: pinned versions and a reproducible environment
The phrase “works on my machine” means the app depends on state that is not in the repository. There are 7 layers to pin: the language runtime, the package manager, dependencies through a committed lockfile, service versions, system libraries, configuration through an env example, and data through a seed.
When a developer says “it works on my machine”, the difference usually sits in one of the rows below, and each row moves one of those places into the repository.
| Layer | What drifts | What pins it | The file |
|---|---|---|---|
| Language runtime | The Node.js or Python version | A version file the version manager reads; npm’s engines field states the range, but without engine-strict it only warns when the package is installed as a dependency | .nvmrc (nvm), .node-version (fnm), .python-version (pyenv), engines in package.json |
| Package manager | npm, pnpm or Yarn, and which version | The packageManager field, with Corepack enabled | package.json |
| Dependencies | Versions resolved on the day someone installed | A committed lockfile and an install that respects it | package-lock.json, pnpm-lock.yaml, poetry.lock, uv.lock |
| Services | Database and cache versions | An image tag or digest on each service | The compose file |
| System libraries | Native build tools and libraries the app links against | A list in the guide, or a dev container or Nix shell | README, devcontainer.json, shell.nix |
| Configuration | Variables one developer added and nobody else has | The env example | .env.example |
| Data | Rows that exist only in the author’s database | The seed | The seed script |
Two rows need a sentence more. Corepack’s README says to set your package’s manager with the packageManager field in package.json and to run corepack enable to install the required Yarn and pnpm binaries on your path. It is distributed with Node.js from version 14.19.0 up to, but not including, 25.0.0, so on newer Node.js releases the guide has to list it as a prerequisite, which is my working rule rather than anything the README says. For services, Compose’s image field takes the form [<registry>/][<project>/]<image>[:<tag>|@<digest>]. My working rule is a tag that names a version on every service, never an untagged image.
CI is the second machine everyone already has. My working rule: if CI builds from a bare runner with the same setup command, nobody can say it only works on their machine, because the build has just shown the repository alone is enough.
The lockfile row can fail quietly. In a medical app I audited, the repository committed two lockfiles for two package managers, one real and one empty, so which tool the host used decided whether anything was pinned. The fix is one package manager, one lockfile, declared in the repository.
Python lockfile, Poetry vs pip, and virtualenv vs pyenv
A Python lockfile records the exact versions a project resolved, and PEP 751 (status Final, resolved 31 March 2025) defines one standard format, pylock.toml. The tools split into 4 jobs: pyenv switches interpreter versions, venv or virtualenv isolates packages, pip installs, and Poetry or uv resolve and lock. Virtualenv versus pyenv is not a choice: they work at different layers.
The jobs get confused because some tools do two of them. pyenv “lets you easily switch between multiple versions of Python” and reads a per-project .python-version file. uv can also install Python versions, and Poetry has a poetry python command group, introduced in 2.1.0 and described in its docs as “an experimental feature”. For isolation, the venv documentation describes lightweight virtual environments, each with their own independent set of Python packages, created on top of an existing Python installation. virtualenv is the third-party tool; its docs say a subset of it has been in the standard library as the venv module since Python 3.3. pip installs. Poetry, uv and Pipenv resolve and lock.
Does pyenv create a virtual environment? No. Its README lists “Manage virtualenv” under the things pyenv does not do, and adds “you can create virtualenv yourself, or pyenv-virtualenv to automate the process”. pyenv-virtualenv is a pyenv plugin that manages virtualenvs and conda environments on UNIX-like systems. That is why virtualenv and pyenv are not rivals: one picks the interpreter, the other isolates a project’s packages on top of it.
PEP 751 proposes a file format for specifying dependencies to enable reproducible installation, named pylock.toml. It sits beside the lock files each tool already writes: poetry.lock, uv.lock and Pipfile.lock. Two tools’ docs describe writing it: pip lock, which the docs label “EXPERIMENTAL”, writes pylock.toml by default, and uv can export its lockfile with uv export --format pylock.toml. If you stay on requirements files, pip’s hash-checking mode (--require-hashes, added in version 8.0) “uses local hashes, embedded in a requirements.txt file, to protect against remote tampering and network issues”.
The Python Poetry vs pip question compares tools that do different jobs. pip installs packages; Poetry manages the project, its dependencies and its lock file, and Poetry’s basic usage guide says “Application developers commit poetry.lock to get more reproducible builds.” uv’s lock and sync behavior, including the --locked and --frozen flags, is in uv’s locking and syncing docs.
| Tool | Picks the Python version? | Isolates packages? | Resolves and locks? | The lock file it writes |
|---|---|---|---|---|
| pyenv | Yes, per project, from .python-version | No: the README says it does not manage virtualenv | Not stated in pyenv’s docs | Not stated in pyenv’s docs |
| venv | No: it builds on an existing “base” Python | Yes | Not stated in the venv docs | Not stated in the venv docs |
| virtualenv | Selects an installed one with --python or -p; by default, the one it runs under | Yes: “isolated Python environments” | Not stated in virtualenv’s docs | Not stated in virtualenv’s docs |
| pip | Not stated in pip’s docs | Not stated in pip’s docs | Experimental: pip lock | pylock.toml (the experimental pip lock default) |
| Poetry | Yes: poetry env use; installing one (poetry python install, 2.1.0) is experimental | Yes: environment isolation is “one of its core features” | Yes | poetry.lock |
| uv | Yes: uv python install | Yes: its project commands manage the virtual environment automatically | Yes | uv.lock, exportable to pylock.toml |
| Pipenv | Yes: python_version in the Pipfile; it installs that version when pyenv or asdf is available | Yes: it creates and manages virtual environments | Yes | Pipfile.lock |
My working rule for this page’s purpose: pick one tool, commit its lock file, and make the setup command use the frozen install. Which one you pick matters less than the team picking the same one.
A Nix development environment, dev containers, and when either is worth it
A Nix development environment and a dev container both pin the whole toolchain, not only the packages: a shell.nix under version control with Nixpkgs pinned to one commit, or a devcontainer.json that defines the container. In my reading, either earns its cost when system libraries or several services are involved.
The Development Containers specification is “An open specification for enriching containers with development specific content and settings”, and a dev container “allows you to use a container as a full-featured development environment”. Its first format, devcontainer.json, is a JSON with Comments metadata format that tools can use to store the configuration needed to develop inside local or cloud-based containers. A Nix shell gets there from the package side: nix.dev’s declarative shell tutorial shows how to put the environment definition under version control and reproduce it on other machines, and its pinning tutorial fetches Nixpkgs as a tarball specified by a Git commit hash to make the result fully reproducible.
The costs differ, in my reading: a dev container needs a container runtime on every machine, and Nix needs someone on the team who has learned Nix. My working rule: either is worth it when the project needs system libraries or several services that are hard to install, or when contractors rotate often. For a Next.js app with a hosted database, a version file, a lockfile and the setup command are enough. Either way, the one command stays the entry point.
Developer onboarding: the first day for the next engineer
Developer onboarding on a one-codebase team is mostly the repository doing its job. The first day has 7 items: least-privilege accounts ready, clone to running by the guide, tests passing, the codebase tour, the documents located, every question written back into the guide, and two numbers recorded: minutes to running and days to first merged change.
Onboarding software developers onto a product one team built and another will run starts in the repository, not in a slide deck. This is the repository’s side of the first day, and every item is my working rule:
- Accounts created before day one, with least privilege: the repository, the staging host, the staging database, the error tracker and the task board. No production secrets on day one.
- Clone to running by the guide alone, timed.
- The tests pass locally.
- The recorded codebase tour, if one exists.
- The documents that answer “where is that” located.
- Every question the newcomer had to ask written back into the guide.
- Two numbers recorded: minutes from clone to running, and days to first merged change.
For dev onboarding on a small team, those two numbers are the only dashboard I’d keep: the first says whether the repository does its job, and the second whether the rest of the handover did. If the tour does not exist yet, making one is its own topic: what is a codebase walkthrough. The first reviewed change goes through a code review checklist.
The owner’s side is a separate job: booking the walkthroughs, answering why things are the way they are, and handing the work over in order. That is covered in developer onboarding when there is no engineering team. When onboarding engineers who join as contractors for a few weeks, the timed clone matters even more, because the first day is a bigger share of the engagement. The same list scales up to engineering onboarding at a larger company; the accounts list grows, and the repository’s side stays the same.
In my reading, developer onboarding tools for a small team are the setup script, a dev container and an access checklist. HR onboarding platforms and their multi-month plans are a different subject.
How to verify it: test setup from a fresh clone on a clean machine
Local setup is verified from a fresh clone on a clean machine in 6 steps: a machine that has never had the project, a tester who did not write the guide, a stopwatch from git clone, a log of every deviation, a pass when the app, login, core flow and tests work, and repeats until the log is empty.
Anyone on the team can test the setup docs on a clean machine; the one rule is that nobody helps the guide along.
- 01 Get a clean machine: a new operating system user account, a fresh virtual machine or cloud development environment, or a laptop that has never had the project. The author's own machine does not count.
- 02 Pick a tester who did not write the guide. They follow only the guide and ask no questions.
- 03 Start a stopwatch at
git clone. - 04 Log every deviation: a missing prerequisite, a command that failed, a value they had to ask for. Each one is a defect in the guide, not in the tester.
- 05 Pass: the app runs, the fixture login works, the core flow completes and the test suite passes, inside the target time.
- 06 Fix the guide and the script, then repeat from a clean state until the log is empty.
The clone is an ordinary git clone of the repository’s URL, run by the tester, not copied from the author’s disk. A deviation log can be a four-column table. These rows are illustrative:
| Step | What the guide said | What was needed | Fixed? |
|---|---|---|---|
| Install | Run the setup command | The Node.js version in .nvmrc, which the tester did not have | Yes: the version manager added to the prerequisites |
| Env file | Copied from .env.example | A payment provider test key, asked for in chat | Yes: the example now says where the test key comes from |
| Migrations | The script runs them | The database was still starting | Yes: the script waits for the database |
| Login | Fixture user in the README | The seed never created that user | Yes: the seed creates it |
To keep it true, my working rule is a CI job that runs the setup command on a bare runner on a schedule, after installing only what the guide lists as prerequisites: the runtime the version file names and the container runtime. Step 1 of the command stops on any other runtime version, and the job goes through the smoke check and then stops the app, so drift shows up as a red build instead of a lost first day. The evidence to keep is the time, the final empty deviation log, the CI run, the date and the machine used.
The Production Hardening Sprint verifies deliverable 10.10 this way: go from clone to running application on a new machine by following the guide alone. Deliverable 10.4 is verified this way: follow the README from a clean checkout and trace a core feature through its documented modules.
Where the sprint does this
Deliverable 10.10 is the one set out under the first heading of this page. Around it, deliverable 10.4 organizes the codebase consistently and explains its layout in the README; deliverable 4.10 provides a repeatable seed script with realistic non-sensitive test records; deliverable 7.11 keeps the application in a version-controlled repository and on a hosting account the founder controls, deployable through a documented pipeline, independent of the tool that generated it; and deliverable 13.1, the production readiness report, is verified this way: account for all 123 IDs; keep failures visible until resolved and explain genuine non-applicable items. The starting point is the app’s current framework and hosting setup, and components are refactored or replaced where the production work requires it. Each line is on the published scope.
Common questions about local setup and Python tooling
Is UV replacing pip?
Not by anything stated in either tool’s docs. uv offers its own project workflow plus a pip interface that its docs call “a drop-in replacement for common pip, pip-tools, and virtualenv commands”, and pip has its own experimental pip lock command. For a repository, which one the team uses matters less than committing the lock file.
Should I use UV or poetry?
Both resolve and lock dependencies, and both can install Python versions: uv with uv python install, and Poetry with poetry python install, which its docs mark as experimental and which arrived in 2.1.0. The choice matters less than committing the lock file and using the frozen install in the setup command.
Should I commit pipfile lock?
Yes. Pipenv’s docs say Pipfile and Pipfile.lock “should be committed to version control to ensure consistent environments across development and deployment”. Poetry gives application developers the same advice about poetry.lock.
Is venv still used?
Yes. venv is part of the standard library, and its documentation says that since Python 3.5 “The use of venv is now recommended for creating virtual environments.”
How do I run a git clone?
Run git clone followed by the repository’s URL; git’s docs describe the command as “Clone a repository into a new directory”. Then run the setup command from inside that directory. If access is denied, the account or key was never added, which in my reading means the first item on the first-day checklist was skipped.
Owning an app means being able to run it, change it and recover it without guessing. The sprint below leaves you with the runbooks and documentation to do that.
Built it with AI. Now it has to hold up for real customers.
The Production Hardening Sprint takes the app you already have and builds the production foundation underneath it. Authentication and access rules, payments that stay consistent, error handling, monitoring, backups, automated tests and a documented handover. Our engineers work inside your existing codebase for ten working days. All 123 deliverables are included, and you get the evidence for each one.
See the Production Hardening Sprint →
$2,500 fixed price · 10 working days · One codebase