Stop Burning Sprints on Laptop Setup: A Paved-Road Dev Environment That Just Works

Standardize your development environment, kill setup friction, and make “works on my machine” a relic. Favor boring defaults. Ship faster.

Make the right thing the easy thing. Everything else is friction tax you don’t need to pay.
Back to all posts

The week we lost onboarding to “works on my machine”

A few summers ago, I watched a unicorn’s new hire cohort burn a full sprint installing Homebrew packages, chasing Python versions, and fighting a Zsh plugin that a staff engineer swore was “essential.” Half the team was on M1 Macs; the org wiki still assumed Intel. CI ran Ubuntu 20.04; laptops were all over the place. We shipped nothing. Meanwhile support tickets spiked because prod hotfixes were stalled behind broken laptops.

I’ve seen this movie at banks, gaming studios, and FAANG-adjacent shops. The cure isn’t another bespoke bootstrap script. It’s a paved road: a standardized development environment you can clone and go with in under an hour. Boring tech. Fewer knobs. Defaults that match CI and production expectations.

What the paved road looks like

A paved-road environment optimizes for flow and consistency over personal preference.

  • Containerized dev shell: devcontainer or a Docker image that mirrors CI.
  • One-liners, not runbooks: make bootstrap, make test, make up.
  • Pinned toolchains: asdf or mise with committed versions.
  • Local deps as services: docker compose for Postgres/Redis/etc., not local installs.
  • Pre-commit hygiene: pre-commit for lint/format/security; same hooks in CI.
  • Minimal editor assumptions: VS Code devcontainer is the default; everything else is “bring your own.”

You don’t need to solve every edge case. You need a reliable default that 80% of the codebase uses.

Before/After: the boring defaults that moved the needle

Here’s what we’ve measured at GitPlumbers after standardizing envs for teams from Series A to public:

  • Onboarding time: median down from 2–3 days to 2–4 hours (time-to-first-PR).
  • CI-to-local parity: build failures due to “works on my machine” drift dropped 60–70%.
  • Flake rate: integration test flakes cut 30–40% after local Compose matched CI services.
  • Support load: internal tooling tickets down 35% within two months.
  • Release cadence: more small PRs, fewer fire drills; DORA lead time improved by ~20% in one quarter.

The cost? A few days to create the template, a week to pilot, and the discipline to say no to bespoke snowflakes.

The minimal toolkit (with copy-pasteable snippets)

Start with the smallest thing that could possibly work. Containers, a Makefile, pinned tools, and pre-commit.

  • Devcontainer: instant dev shell that mirrors CI
{
  "name": "webapp",
  "image": "mcr.microsoft.com/devcontainers/python:3.11-bullseye",
  "features": {
    "ghcr.io/devcontainers/features/node:1": { "version": "18" }
  },
  "postCreateCommand": "make bootstrap",
  "customizations": {
    "vscode": {
      "extensions": ["ms-python.python", "esbenp.prettier-vscode"]
    }
  },
  "containerUser": "vscode",
  "mounts": [
    "source=${localWorkspaceFolder}/.cache,target=/workspaces/.cache,type=bind,consistency=cached"
  ]
}
  • Local services via Compose
# compose.yaml
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: postgres
    ports: ["5432:5432"]
  cache:
    image: redis:7
    ports: ["6379:6379"]
  • Pin tool versions with asdf (or mise)
# .tool-versions
python 3.11.6
nodejs 18.18.2
terraform 1.7.3
  • One-liners via Makefile
# Makefile
.PHONY: bootstrap test lint up

bootstrap: ## Install tools and pre-commit hooks
	@asdf plugin add nodejs || true
	@asdf install
	@pip install pre-commit
	@pre-commit install

test:
	@pytest -q

lint:
	@pre-commit run --all-files

up:
	@docker compose up -d
  • Pre-commit hygiene that matches CI
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 23.9.1
    hooks:
      - id: black
  - repo: https://github.com/pre-commit/mirrors-eslint
    rev: v8.55.0
    hooks:
      - id: eslint
  • CI image that mirrors dev (GitHub Actions example)
# .github/workflows/ci.yml
jobs:
  test:
    runs-on: ubuntu-22.04
    container:
      image: ghcr.io/acme/webapp-ci:python3.11-node18
    services:
      db:
        image: postgres:15
        ports: [5432]
      cache:
        image: redis:7
    steps:
      - uses: actions/checkout@v4
      - run: make lint test

If your repo is polyglot, resist tool sprawl. Keep the paved road thin; let teams extend in make local-* targets, not by forking the template.

Trade-offs: devcontainers, Nix, and bespoke scripts

I’ve tried them all. Here’s the real talk:

  • Devcontainers (VS Code)

    • Pros: Fastest path for most teams; great onboarding UX; easy to encode extensions and tasks. Mirrors CI with minimal effort.
    • Cons: VS Code bias; Docker Desktop licensing on macOS/Windows; resource heavier. Use colima on macOS if you want to dodge Docker Desktop.
  • Nix/Nix flakes

    • Pros: Reproducibility nirvana, cross-editor, works without Docker. Great when you have infra folks who love it.
    • Cons: Steep learning curve; you’ll become tech support unless the org commits. Avoid for your first iteration unless you already have Nix fans.
  • Bespoke shell scripts + Homebrew

    • Pros: Feels simple at first; everyone knows Bash.
    • Cons: Rot, drift, and subtle bugs. ARM vs x86 pain. Silent upgrades break CI. I’ve never seen this scale past 2–3 squads without tears.
  • Terraform, ArgoCD, GitOps for shared dev infra

    • Pros: If you offer a shared dev cluster (Kubernetes with Istio/Prometheus), GitOps via ArgoCD keeps it sane.
    • Cons: Don’t let platform complexity leak into laptops. Keep the local loop simple; save Kubernetes for integration or preview environments.

Rollout plan: 90 days to paved-road

You don’t need a platform org. You need a small task force and a deadline.

  1. Week 1–2: Baseline and pick a reference repo

    • Inventory languages, CI images, and local dependencies.
    • Choose one high-impact repo (active, representative stack).
  2. Week 3–4: Ship v1 devcontainer + Makefile

    • Mirror the CI container; add make bootstrap test up.
    • Pin tools with asdf and commit .tool-versions.
  3. Week 5–6: Pilot with one squad

    • Measure time-to-first-PR; gather friction points.
    • Fix slow builds with Docker layer caching; document gotchas.
  4. Week 7–8: CI parity + hygiene

    • Run lint/test via make in CI; enforce pre-commit.
    • Add compose services to match CI and local.
  5. Week 9–10: Template and docs

    • Extract into a template or cookiecutter. One-page README: prerequisites, commands, troubleshooting.
  6. Week 11–12: Rollout and sunset

    • Migrate 3–5 more repos; deprecate old bootstrap scripts.
    • Track metrics: onboarding time, CI failure classes, flake rate, internal tickets.

This is the moment to say no. If someone wants Fish shell tweaks and custom dotfiles, make it opt-in. The paved road stays boring.

Guardrails without handcuffs

You can keep developers fast and your security team calm.

  • Secrets: Use 1password CLI, aws-vault, or sops-encrypted files. Never bake secrets into the image.
  • Dependency scanning: Add trivy and SBOMs (syft) to CI; surface results locally with a make audit.
  • Policy via code: Enforce branch protections and required checks. Don’t write a PDF policy doc; write a make verify.
  • AI-generated code reality: We’re seeing PRs from Copilot/ChatGPT with mismatched toolchains and broken scripts. The paved road catches this early because hooks and tests run locally and in CI the same way. If you’re drowning in “vibe-coded” scripts, standardize first, then do a vibe code cleanup pass.
  • Performance: Keep images slim; use multi-stage builds. Example Dockerfile for app runtime:
FROM python:3.11-slim AS base
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN pip install poetry && poetry config virtualenvs.create false \
    && poetry install --no-dev --no-interaction
COPY . .
CMD ["gunicorn","app:app","-b","0.0.0.0:8080"]
  • Editor choice: Default to VS Code devcontainer because it removes the most friction. Don’t ban other editors; just don’t staff them.

Lessons learned and what I’d do differently

  • Start with one stack and one repo. Win there, then templatize. Avoid boiling the ocean.
  • Match CI exactly. Same base image, same make targets, same Compose services.
  • Keep the paved road thin. Make it easy to extend locally without forking the template.
  • Make the right thing the easy thing. One command to bootstrap, one to test, one to run.
  • Measure relentlessly. If onboarding isn’t under a day by month two, something’s off.
  • Document the boring stuff. One page. Screenshots. No “tribal knowledge.”

If you’re stuck with a thicket of bespoke scripts and AI-generated glue code, GitPlumbers has done this cleanly at banks, fintechs, and fast-moving SaaS orgs. We’ll help you set the paved road, migrate repos, and put “works on my machine” in the museum where it belongs.

Related Resources

Key takeaways

  • Paved-road environments reduce onboarding from days to hours and eliminate class-of-bug drift between laptops and CI.
  • Favor containers + a `Makefile` + `asdf` + `pre-commit` over bespoke install scripts and tribal docs.
  • Devcontainers are the fastest path for most teams; Nix is powerful but has a steeper learning curve; bespoke scripts rot.
  • Measure success with time-to-first-PR, CI-to-local parity, flake rate, and support ticket volume.
  • Roll out in 90 days: pick a reference repo, ship a v1 devcontainer, pilot with one squad, then templatize.

Implementation checklist

  • Decide on your paved road: devcontainer + Docker Compose + Makefile.
  • Pin tool versions with `asdf` (or `mise`) and commit them.
  • Add `pre-commit` for lint/format/security checks; run them in CI.
  • Mirror CI Docker image locally; keep parity across environments.
  • Create a `make bootstrap` and `make test` that works first try on a clean machine.
  • Document one page: prerequisites, commands, troubleshooting.
  • Sunset bespoke scripts; guardrails via code review and templates, not policy docs.

Questions we hear from teams

Can we do this without forcing everyone onto VS Code?
Yes. Default to devcontainers because they remove the most friction for the most people. If some developers prefer Neovim/JetBrains, they can attach to the running container or use Nix. Don’t staff alternative paths; support the default.
What about teams already deep into Nix?
If you have Nix competence, it’s a great paved road—reproducible, editor-agnostic. Start with a flake that installs the same toolchain CI uses. The trade-off is ramp-up and maintenance. For orgs new to Nix, we recommend devcontainers first, then consider Nix later.
How do we keep secrets safe locally?
Use short-lived credentials and tools like 1Password CLI, aws-vault, or sops. Never bake secrets into images. Provide `make login` targets that fetch credentials on demand and expire automatically.
Will this slow down local dev because of Docker?
Not if you keep images slim, use layer caching, and avoid heavy Docker Desktop defaults. On macOS, Colima performs well. For CPU-heavy tasks, run tests inside the container to maintain parity and predictable performance.
How do we prevent template drift over time?
Treat the template like code: owners, versioned releases, CHANGELOG, and an upgrade guide. Periodically run repo-wide updates via tooling (e.g., Renovate for base images, a simple script for Makefile targets). Enforce deviations via lightweight design review.

Ready to modernize your codebase?

Let GitPlumbers help you transform AI-generated chaos into clean, scalable applications.

Get a Paved Road Assessment Download the devcontainer starter

Related resources