Course outline · 0% complete

0/29 lessons0%

Course overview →

Why software breaks on other machines

lesson 1-1 · ~12 min · 1/29

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 has 2.18
  • System tools: a font, a C library, or ffmpeg that 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.

app.py (identical) laptop Python 3.12 openssl 3.2 works server Python 3.8 openssl 1.1 crashes
The same source code meeting two different environments. The code is identical, the installed versions are not, which is the whole reason "works on my machine" happens.

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.

DetailWhy it matters
fi closes an ifbash 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.