Python's open() Default Encoding Trap: The Config Loader That Works on Dev and Explodes in Docker

2026-09-01

This function loads a newline-delimited JSON file of users. It works flawlessly on the developer's laptop, passes CI, and ships to production. Three hours after deploy, a customer named François signs up and the entire onboarding worker dies.

import json

def load_users(path):
    """Read a JSONL file of user records."""
    users = []
    with open(path) as f:
        for line in f:
            users.append(json.loads(line))
    return users


def greet_all(path):
    for user in load_users(path):
        print(f"Hello, {user['name']}!")


if __name__ == "__main__":
    greet_all("users.jsonl")
    # UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3
    #   in position 17: ordinal not in range(128)

The Bug

Python's open() in text mode does not default to UTF-8. It defaults to whatever locale.getpreferredencoding(False) returns for the current process. On the developer's Mac or Ubuntu machine, that's almost always UTF-8. Inside a minimal Docker image — python:3.11-slim, alpine, distroless, anything without a full locale package — the C library reports the locale as C or POSIX, and the preferred encoding becomes ASCII.

So the same code path does two different things:

The failure mode is nasty because it depends on data, not code. Every ASCII-only user works. The bug hides behind whichever test fixture your CI happens to generate. It surfaces the moment a real customer types a character above U+007F — and by then you're staring at a traceback pointing at a line that "obviously" just reads a file.

Windows adds its own flavor: the default there is often cp1252, which silently mis-decodes UTF-8 bytes into mojibake instead of raising. That's arguably worse — no exception, just corrupted names in your database.

The Fix

Always pass encoding= explicitly when opening text files. Never rely on the locale.

def load_users(path):
    users = []
    with open(path, encoding="utf-8") as f:
        for line in f:
            users.append(json.loads(line))
    return users

Additional defenses worth knowing:

The rule is simple: text mode without an explicit encoding is a bug waiting for a customer whose name has an accent.

Key Takeaway: Python's open() in text mode uses the locale's preferred encoding, not UTF-8 — always pass encoding="utf-8" so your code doesn't depend on whether the container image happened to install a locale.

All newsletters