Build1 publisher2 min readPublished
Python's default venv reaches the system interpreter through a 16-byte symlink
Python's venv module links a new environment to the system interpreter through a 16-byte symlink, a dev.to walkthrough on Ubuntu 24.04 shows. A distro upgrade to that Python can therefore break the environment or change what it runs without warning.
The Engineer · Build desk

What happened
- Inside the venv, the json module loads from /usr/lib/python3.12 and sys.base_prefix reports /usr, so the standard library still comes from the system install.
- The venv's pyvenv.cfg records home = /usr/bin, version 3.12.3 and executable /usr/bin/python3.12, and Python reads that file at startup to find its home.
- The walkthrough ran on a throwaway Ubuntu 24.04 cloud VM with Python 3.12, where the venv module ships as the separate python3-venv apt package.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Pinned package versions stop at site-packages, so stdlib modules such as ssl change on the distro's patch schedule, outside any lockfile review.
- exposure Every plain venv on a host follows the same /usr/bin/python3 name, so one distro change to that link moves all of them to a different interpreter together.
- decision Teams shipping plain venvs have to choose between owning the base interpreter's version and checking at deploy time that it still matches the pyvenv.cfg that built the environment.
The link sizes match their targets exactly. The string /usr/bin/python3 is 16 characters and python3 is seven, the same sizes as the links in the post's ls output [1]. The file they end at, /usr/bin/python3.12, is 6,939,856 bytes [6]. Linking is a sensible default. Neither the interpreter nor the standard library gets copied into a new environment [1][2].
The detail I would flag at review is which name the link targets. The venv's python3 points at /usr/bin/python3, a symlink the distro owns, and only the next hop reaches the versioned binary [7]. The venv's pyvenv.cfg records version = 3.12.3 and executable = /usr/bin/python3.12 [8]. So the environment holds two records of its interpreter: a config line naming a versioned file, and a link that follows wherever the distro points python3 [3]. The dev.to post warns that when the system Python is upgraded, moved or deleted, "your venv can break outright, or keep running against a different Python without telling you" [3].
The standard library is the second coupling. In the post's output, sys.prefix is /home/ubuntu/demo, where packages get installed, and sys.base_prefix is /usr, where the interpreter and stdlib come from [10][11]. Modules including os, json, asyncio and ssl load from /usr/lib/python3.12 [12]. A pinned requirements file governs site-packages and nothing else [4]. When the distro ships a patched Python, the ssl code every venv on that host imports is replaced, and the venv's installed packages stay exactly as they were [4].
The evidence is one Ubuntu 24.04 VM running Python 3.12 [4]. The post states that macOS and other distros show the same pattern, but its terminal output covers only the Ubuntu run [1][4].
In my context, a service in a plain venv on a host that takes routine distro updates, the base interpreter is a dependency. It gets its own pin and its own upgrade window. The post's own output supplies a cheap drift check. On the fresh box, the realpath of the venv's python is /usr/bin/python3.12, the same path as the executable line in pyvenv.cfg [10][8]. A deploy step that compares the two catches a repointed /usr/bin/python3 before the service starts on another interpreter [5]. A patch release installed at the same /usr/bin/python3.12 path passes that check. The step also has to compare the version line in pyvenv.cfg against what the venv's python reports [5].
What to watch
- A rerun of the post's ls, stat and pyvenv.cfg checks on macOS, where the post asserts the same pattern but shows no output.
- An in-place distro upgrade that repoints /usr/bin/python3 under existing venvs would show whether they break outright or run silently on the new version.
- The post's Part 3, on building a venv that does not depend on the system interpreter, and whether its fix covers the stdlib as well as the binary.