A bug that passes on one laptop and fails on the staging box every time is rarely a code bug. The explanation often contains no code at all: a runtime two versions behind on one machine, a DATABASE_URL that lives only in an uncommitted .env, a database major version the migration didn't expect. Nobody touched the code. The machines simply wandered apart.
That drift is fixable. This is how to keep your development environment synced across multiple machines: define the environment inside the repository with a dev container, move secrets into a secrets manager, handle personal config with dotfiles or a sync tool, and treat your AI coding setup as part of the environment. Get those four right and every machine behaves like the same machine.
The four layers that drift
Most "works on my machine" pain comes from four specific layers, not vague messiness. Name them and you can fix each with the right tool instead of reinstalling everything in a panic.
Toolchain versions cause the loud failures. Runtimes, compilers and CLIs drift slowly, usually after a partial upgrade on one device, and they fail visibly: the wrong Node version, a missing compiler, a CLI subcommand that doesn't exist yet.
Secrets fail more quietly. API keys, database URLs and tokens live in .env files that never get committed, so every machine keeps its own incomplete copy — and the symptom is an auth error that looks exactly like a code bug.
Personal config is quieter still. Shell prompt, aliases, git config and editor settings drift as you tweak whichever machine you're at that week. Nothing breaks; things just feel wrong.
Agent setup is the newest layer and the most neglected. MCP servers, agent skills and assistant configuration are part of the environment now, and they drift like everything else — you notice only when an agent can't reach a tool it reached yesterday.
The first two break builds. The last two break your sense of what's actually installed.
Anchor the environment to the repo with dev containers
A dev container is the highest-leverage move, because it turns the environment definition into a file that travels with the code. Add a .devcontainer/devcontainer.json to the repository and commit it. Any machine — your laptop with Docker, or a cloud environment such as GitHub Codespaces — can then build the same toolchain from that definition.
What the spec solves is version pinning. devcontainer.json fixes the base image, the toolchain versions and even the editor extensions, so a local container and a cloud one resolve to the same environment from the same file, with no second source of truth to drift. A repository can carry several dev container configurations in subfolders, which matters in a monorepo where the frontend and the data pipeline genuinely need different toolchains.
Keep the definition honest with rebuilds
A dev container only stays accurate if you rebuild it when the definition changes. Bump a version in devcontainer.json and running containers won't see it until they're rebuilt — from your editor's "Rebuild Container" command locally, or gh codespace rebuild for a codespace.
If it's not in devcontainer.json, it doesn't exist. Install something ad hoc and you've created a machine that lies to you.
Watch cloud costs and machine sizes
If part of your setup runs in the cloud, capacity and cost become part of the problem. Cloud dev environments bill by usage, which is fine for bursts and expensive as an always-on habit, and a machine type that's too small to run your test suite is its own kind of drift. A sensible split: dev containers locally for daily work, a cloud environment for travel or a borrowed machine. Check your provider's current pricing and set a spending limit before a team adopts it.
Secrets: the one thing the repo must never hold
Secrets are the layer people forget when they first try to sync a dev environment, and the one that bites hardest. The environment definition can't contain real secrets, and that's where most setups quietly break: someone copies a .env file by hand once, forgets it after a key rotation, and loses an afternoon to an auth error that has nothing to do with the code.
A dedicated secrets manager — Infisical, Doppler, 1Password, HashiCorp Vault, or your cloud provider's — fixes this with a pull model: the environment fetches secrets when it's created or started, instead of each machine keeping loose files. A key rotation then reaches every machine on the next pull, not whenever someone remembers to copy it around.
If a secret exists in more than one place, one of those copies is already wrong.
Personal config: dotfiles without ceremony
Dotfiles are the layer nobody else sees but you feel constantly. There are two realistic paths.
A dotfiles repository with a manager. Tools like chezmoi or GNU Stow keep config files in version control and apply them on a new machine. Cheap and durable — but it syncs files, not state, and it won't handle secrets or machine-specific differences without extra work.
An environment sync service. Account-based tools sync more of the surface — configs plus packages, environment variables and app settings — through push and pull, usually with encryption. The trade is transparency: part of your source of truth moves into someone else's service, so check that you can export it to plain files.
We compare the two approaches in detail in dotfiles manager vs environment sync tool.
Your agent setup counts as environment now
If Claude Code, Codex or Cursor is part of your workflow, a machine missing an MCP server the project depends on is the same class of problem as a missing database version. Agent config deserves the same discipline as the toolchain.
Split it the same way: skills and MCP servers the project needs belong in the repository (project-scoped skills in .claude/skills/, for example), and your personal agent setup belongs in your sync layer. A dotfiles repo can copy those files, but MCP configuration is written differently for each coding tool, and a skill copied to a machine without the right client does nothing.
That's the layer loadout covers. A background agent on each paired machine installs, toggles and removes skills and MCP servers from one dashboard, and writes each MCP entry in the format the coding tools it detects on that machine expect. It's narrower than a whole-machine sync tool, and deeper on exactly the layer that keeps drifting. See how syncing works, and if you're still choosing what to standardize on, start with the best MCP servers for Claude Code or how to add skills to Claude Code.
A weekly drift check
Keeping machines in sync isn't a one-time project; it's a habit with a small recurring cost:
- Rebuild one environment from scratch. Pick a machine or a fresh dev container and build it from the repository's definition alone. If anything fails, the definition is missing something.
- Diff your
.envagainst the secrets manager. Confirm every local value matches the source of truth, then delete anything local-only. - Commit config changes. Any tool, alias or editor tweak from this week goes into the dotfiles repo or sync tool before Friday.
- Check agent parity. Compare the MCP servers and skills on each machine with what the project expects.
- Compare versions. Print your toolchain versions (
node -v,python --version, and so on) on two machines and diff the output.
Sequence the fix around where drift hurts
There's no single right order; the first move depends on where the pain lands. Working solo across a couple of repositories? Start with devcontainer.json — it removes toolchain drift at the root, and every machine benefits from one commit. If the problem is onboarding, where new machines or new hires take days to become productive, a secrets manager pays back fastest, because it deletes the undocumented .env copying everybody eventually gets wrong. Dotfiles matter most once you've stopped losing whole days and just want the small frictions gone. Agent config comes last for most teams — unless a coding agent is your primary workflow, in which case it moves near the top.
Whatever the order, the end state is the answer to how to keep your development environment synced across multiple machines: setting up a new machine should be a pull and a rebuild, not an evening of reinstalling things from memory.
FAQ
What is the best way to keep a development environment synced across multiple machines?
Define the toolchain in the repository with a dev container, keep secrets in a secrets manager, sync personal config with dotfiles or a sync service, and include MCP servers and agent skills in your sync layer.
Should I sync .env files between machines?
No. Keep secrets in a dedicated manager and pull them when the environment starts, so every machine gets the same values without manual copying.
Do dev containers work without GitHub Codespaces?
Yes. devcontainer.json is an open specification; you can build the same container locally with Docker and a supporting editor, and use a cloud environment only when you need one.
How do I keep MCP servers and skills the same on every machine?
Keep project-level skills in the repository, and use a tool like loadout for the rest: it installs the same skills and MCP servers on every paired machine and writes each MCP entry in the format that machine's coding tools expect.