Why software breaks on other machines
You already know the terminal and git. Git moves your code between machines perfectly, every byte identical. And yet a program that runs fine on your laptop can crash the moment it lands on a server or a teammate's machine.
That is because a program never runs alone. It runs inside an environment: the operating system, the language version (Python 3.12 vs 3.8), the installed libraries, the database version, and configuration like environment variables. Git ships the code but none of that.
When any piece of the environment differs, the same code can behave differently. That is the famous bug report: "works on my machine."
What actually differs
A concrete example. You build an app on your laptop and it works. You copy it to a server with git clone and it dies. Nothing in the code changed. What changed is everything around it:
- OS: Ubuntu 24.04 on your laptop, Debian 10 on the server
- Language version: Python 3.12 vs 3.8, and your code uses a 3.12 feature
- Libraries: you have
requests 2.31, the server has2.18 - System tools: a font, a C library, or
ffmpegthat only you installed - Config: an environment variable set in your shell but not there
Containers solve this by shipping the code together with its environment as one unit, so every machine runs the exact same thing.
The cost is bigger than one crash
The broken server is the dramatic version. The quiet, expensive version happens every week on real teams:
- Onboarding: a new teammate spends their first days following a setup document ("install Python 3.12, then Postgres 16, then...") that is always slightly out of date
- New servers: every extra machine you deploy to must be hand-configured the same way, and each one drifts a little over time
- Ghost bugs: "works on my machine" issues burn hours precisely because the bug is not in the code, so reading the code cannot find it
Containers turn all three into a single docker run. That is why they went from niche tool to the default way software ships in about a decade: the whole industry was paying these costs by hand.
The mismatch, written out
Three echo lines that lay the problem out plainly. This only prints text, but it is the exact situation containers were invented for: same code, two environments.
Every difference on those two lines is invisible to git. A git diff between the machines would come back empty while the program still crashes on one of them.
echo "your laptop: python 3.12 postgres 16 ubuntu 24.04" echo "the server: python 3.8 postgres 12 debian 10" echo "same code, two different environments"
Output
your laptop: python 3.12 postgres 16 ubuntu 24.04 the server: python 3.8 postgres 12 debian 10 same code, two different environments
A version check in bash
The kind of guard a deploy script might run before shipping. Two variables hold each machine's Python version, and an if compares them.
laptop="3.12" server="3.8" if [ "$laptop" = "$server" ]; then echo "environments match" else echo "environment mismatch: laptop $laptop vs server $server" fi
Output
environment mismatch: laptop 3.12 vs server 3.8
Three bash details are worth naming, because they trip up anyone arriving from Python or JavaScript.
| Detail | Why it matters |
|---|---|
fi closes an if | bash uses keywords, not indentation |
= inside [ ] | string comparison, not == |
quotes around "$laptop" | an empty variable would otherwise break the syntax |
Without the quotes, an empty variable collapses the test to [ = "3.8" ], which is a syntax error rather than a false comparison. Variables expand inside double quotes, which is how the echo line prints the values rather than the names.
This is the manual version of what a container makes unnecessary. Once the environment travels with the code, there are no two versions left to compare.
Reading the symptom correctly
When an app works locally, crashes on the server, and git shows the code is byte-identical on both machines, the explanation that fits is that the server's environment differs from yours: its OS, language version, installed libraries, or config.
Git guarantees the code matches and nothing more. Code depends on everything around it, and any mismatch there changes behavior. Containers exist to ship that whole environment alongside the code.
The tempting wrong reads are worth naming, because engineers lose real hours to each of them:
- Git did not corrupt anything. Every object is content-addressed by its SHA hash, so silent corruption is exactly what git is built to make detectable.
- "Identical code always behaves identically" is the assumption that makes this bug so confusing. Code is only half the input, so the crash is not random, it is reproducible on that machine and only that machine.
- Pushing again cannot help. There is nothing left to transfer, which is the clue that the problem lives outside the repository.
The word for it
Everything around your code that it needs in order to run, the OS, the language version, the libraries, and the configuration, is the environment.
It is the word already hiding in a term you know, environment variables. The whole lesson reduces to one equation:
program = code + environment
Git transports only the left-hand term. Containers package both, which is why the same container behaves the same way on any machine that can run it. Keep this framing, because the rest of the course is mechanics for one idea: pinning the environment down and shipping it with the code.