Docker solved the cheap half of "it works on my machine". The expensive half is still wide open.
The runtime is reproducible now: same image, same interpreter, same pinned dependencies, same OS libraries. That class of bug largely went away, and it deserved to.
What nobody containerized is state.
My local database holds rows I created this morning, in the shape the current code expects. Production holds years of accumulated shapes: payment references truncated by an upstream system, partial amounts against an obligation that was supposed to arrive in one transfer, duplicates from a client retry, rows written under a schema two migrations ago and never backfilled.
The code path runs identically in both places. The outcome differs because the input differs.
Same pattern outside payments. A catalog parser works perfectly against the page I opened while writing it, then it meets a regional markup variant, a product rendered in a different template, a listing that is technically valid and structurally nothing like my fixture.
So the honest version of the phrase is not "my machine is different". It is "my data is younger".
What I do about it: I treat production data shapes as first-class test fixtures. The ugly cases (unmatched payments, malformed requisites, odd catalog pages) go into the suite as permanent fixtures, anonymized. I make the code state its assumptions explicitly instead of holding them silently. And when something will not reproduce locally, I stop hunting for a version difference and start asking which row exists there that does not exist here.
Environment parity is a solved problem. State parity is a design decision, and I think most teams never make it.
I go deeper into how I structure this kind of work on my blog: https://polycratia.com/c/753bd780
When a bug refuses to reproduce locally, what do you check first: versions, data shape, or event ordering?