Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild
Back to Blog Explainer

What a Development Environment Actually Is (And Why Yours Drifts)

Sean

Platform Writer

Aug 27, 2026
8 min read

A development environment is not a machine. It is the complete set of things your code runs against - runtime version, dependencies, services, data, and configuration - and the reason the works-on-my-machine joke never gets old is that most teams only control two of those five.

What a Development Environment Actually Is (And Why Yours Drifts)

Every team has a development environment whether they designed one or not. The question is whether it resembles production closely enough that passing locally means anything, and the honest answer for most projects is that nobody has checked recently.

Table of contents

The five things that make up an environment

When people say environment they usually mean the machine. The machine is the least interesting part.

  1. Runtime - the language version and its patch level. Node 20.11 and Node 22.3 are different environments, and so are Python 3.11 and 3.12.
  2. Dependencies - the resolved versions of every package, transitively. A lockfile pins these; a version range does not.
  3. Backing services - the database, cache, queue, and object store the code talks to, including their versions and configuration.
  4. Configuration - environment variables, feature flags, connection strings, and credentials.
  5. Data - the shape and volume of what is in the database.

Drift in any one of these breaks the assumption that local behaviour predicts production behaviour. Most teams pin the second, sometimes the first, and leave the other three to chance.

The fifth is the one nobody controls and the one that causes the most surprising failures. A query with no index is fast against two hundred development rows and a timeout against two million production rows. No amount of runtime parity catches that.

Development, staging, production

The three-environment model is conventional, and each one exists to answer a different question.

  • Development is where you make changes. Optimised for iteration speed: hot reload, verbose logging, seed data, debug tooling. Correctness under load is explicitly not its job.
  • Staging is where you verify. Optimised for resembling production: same runtime, same service versions, same configuration shape, ideally similar data volume. Its only purpose is to make a deploy boring.
  • Production is where users are. Optimised for reliability and observability: real data, real traffic, real consequences.

The common failure is a staging environment that resembles development rather than production. If staging runs a different database version, has a tenth of the data, and skips the CDN in front, then a green staging deploy tells you the code compiles and very little else.

A staging environment that nobody trusts is worse than none, because it costs money and provides false confidence. If yours is in that state, the fix is either to make it resemble production or to be honest that you are testing in production and invest in fast rollback instead.

Some teams add ephemeral per-branch environments - spun up on a pull request, destroyed on merge. That is the strongest version of this idea, because the environment is created from the same definition every time and cannot drift between uses.

Where drift comes from

Drift is not an event, it is an accumulation. Four sources cover almost all of it.

Manual changes. Someone connects to a box to fix an urgent problem, installs a package, edits a config file, and does not write it down. The change works, so nobody notices, until the machine is rebuilt six months later and the fix is gone.

Unpinned versions. A base image tagged with only a major version gets a different patch release depending on when it was built. A dependency range resolves differently on Tuesday than it did on Monday. Both are reproducible builds in name only.

# Drifts - resolves to whatever is current at build time
FROM node:20

# Pinned - the same bytes every time
FROM node:20.11.1-alpine3.19@sha256:...

Local installs. A developer has Postgres 14 from a package manager, production runs 16, and nobody notices until a query uses syntax that changed between them. Running services in containers removes this entire category.

Configuration divergence. Development turns off rate limiting, uses a permissive CORS policy, and disables TLS verification to make local work easier. Each of those is a code path that production exercises and development never does.

That last point is the sharpest one. Every conditional branch keyed on the environment name is a piece of production behaviour you are not testing. They are sometimes necessary; they are never free.

Making local reproducible

The practical target is that a new developer can go from a fresh clone to a running application with one command, and that the result is identical to what everyone else has.

# docker-compose.yml - backing services, pinned
services:
  db:
    image: postgres:16.2-alpine
    environment:
      POSTGRES_PASSWORD: localdev
      POSTGRES_DB: app_dev
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Running backing services in containers is the highest-leverage change available to most teams. It pins the version, isolates the data, and makes a full reset a single command rather than a support ticket.

  • Pin the runtime with a version file and have the toolchain read it.
  • Commit the lockfile and install with the frozen variant, not the resolving one.
  • Keep seed data in the repository as a script, so everyone’s development database has the same shape.
  • Put setup in a Makefile or a scripts entry, so the sequence is executable rather than described in a README that nobody updates.
  • Run migrations the same way locally as in production, so migration bugs surface before deploy.

None of this is exotic, and the payoff compounds. The time saved is not the initial setup - it is the debugging session six months later that turns out to be a version difference.

Parity is a budget, not a binary

Full parity between development and production is neither achievable nor desirable. Production has more machines, more data, real traffic, and real credentials, and reproducing all of that locally would make development unbearably slow.

The useful framing is to decide which differences you accept and write them down. A short list, roughly in order of how much they cost you when they differ:

  1. Runtime version. Never differ. This is free to fix and expensive to get wrong.
  2. Backing service versions. Never differ. Containers make this free too.
  3. Configuration shape. Same variable names, different values. If production reads one connection string and development reads three separate host and port variables, you have two code paths.
  4. Data volume. Accept the difference, but test queries against production-scale data somewhere before shipping.
  5. Traffic and concurrency. Accept the difference. Load testing is a separate exercise, not something local development should attempt.
  6. Credentials and third-party services. Accept the difference. Use sandboxes and test keys.

The first three are cheap to fix and account for most works-on-my-machine incidents. The last three are expensive to fix and are better handled by good observability and fast rollback than by trying to simulate them.

Which is the real point. Parity reduces surprises; it does not eliminate them. The complement to a good development environment is being able to see what production is doing and undo a deploy quickly when it turns out you were wrong.

How this fits the rest of the stack

The environment you cannot reproduce is the one that costs you a weekend. Where RunxBuild fits is the far end of that chain: push a repo and get a build log, a live route, runtime logs, and rollback to the previous deploy, with environment variables held by the platform rather than by a file in the image. The RunxBuild hosting calculator shows a service, a managed Postgres, and storage as separate line items, so standing up a staging copy that actually resembles production is a number you can decide on rather than guess at.

Useful related references:

FAQ

What is the difference between a development and a production environment?

Development is optimised for iteration - hot reload, verbose logging, seed data, and debug tooling - while production is optimised for reliability and observability with real data and real traffic. The important differences are not the machines but the runtime versions, backing service versions, configuration, and data volume.

Do I need a staging environment?

You need somewhere that resembles production closely enough to make a deploy boring. A staging environment that runs different service versions with a tenth of the data provides false confidence and still costs money. If you cannot make it resemble production, invest in observability and fast rollback instead.

What causes environment drift?

Four things: manual changes made directly on a machine and never recorded, unpinned versions in base images and dependency ranges, locally installed services that differ from production, and configuration that disables production behaviour such as rate limiting or TLS verification in development.

Should development use the same database version as production?

Yes. This is one of the cheapest parity wins available - run the database in a container pinned to the production version. Differences between major Postgres or MySQL versions include query planner behaviour and syntax changes that will not surface until deploy.

How close should development be to production?

Match the runtime version, the backing service versions, and the configuration variable names - these are cheap to fix and cause most incidents. Accept differences in data volume, traffic, and third-party credentials, and cover those with observability and rollback rather than trying to simulate them locally.

#development environment#dev vs production#environment parity#staging environment#twelve-factor