Post

A practical container-only philosophy for Software Engineer and AI Agents

A practical container-only philosophy for Software Engineer and AI Agents

Practical Container-Only: Building a Resilient Image Resizer

Following up on the Container-Only Philosophy, I recently had a practical “battle” that perfectly illustrates why this mindset is so powerful—and why some common container habits (like constant rebuilding) are actually anti-patterns for local development.

The task was simple: create a Python script to resize images by a percentage. However, the journey to get there revealed exactly how a “clean” host machine can still try to sabotage a container, and how to build a setup that is truly “Monstro.”

The Sweet Spot: Runtime in the container, logic on the host.


The Trap: Rebuilding vs. Mapping

When people start with Docker, they often follow a “Production Pattern”:

  1. Copy code into Dockerfile.
  2. docker build.
  3. docker run.

This is a mistake for local SWE work. If you change one line of code, you have to wait for a rebuild. It creates friction and kills the flow.

In a true Container-Only local setup, we separate the Engine from the Logic:

  • The Engine (Container): Stays static. It contains the OS and the heavy libraries (like Pillow for images).
  • The Logic (Host): Lives in your project folder. You map it into the container as a volume.

This allows you to edit your script in VS Code or NeoVim and run it instantly. The container provides the “safe room,” but you keep the keys to the “logic” on your host.


Case Study: The Image Resizer

I needed a tool to shrink a 2MB image by 50%. Instead of installing Python 3.11 and the Pillow library on my MacBook, I defined a “Thin Engine.”

1. The Engine (Dockerfile)

1
2
3
FROM python:3.11-slim
RUN pip install --no-cache-dir Pillow
WORKDIR /app

I build this once. It never changes unless I need a new library.

2. The Logic (resize-image.py)

Because different shells (Zsh, Bash, Fish) pass arguments differently, the script needs to be “Agentic” and defensive. It doesn’t trust the host; it cleans the input.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sys, os, re
from PIL import Image

def main():
    # We join all arguments to bypass weird shell "list-nesting"
    raw_input = " ".join(sys.argv[1:])
    clean_input = raw_input.replace('[', '').replace(']', '').replace(',', '')
    parts = clean_input.split()

    input_path = os.path.join("/app", parts)
    factor = float(re.search(r"(\d+)", parts).group(1)) / 100.0

    with Image.open(input_path) as img:
        new_size = (int(img.width * factor), int(img.height * factor))
        resized = img.resize(new_size, Image.Resampling.LANCZOS)
        resized.save(os.path.join("/app", parts), "JPEG", quality=85)
        print(f"✅ Success! Resized to {new_size}")

if __name__ == "__main__":
    main()

Dealing with “Shell Ghosts”

During development, we encountered a fascinating error: the host shell was passing arguments as a string representation of a list: ['data/img.jpg', ... ].

On a standard host-installed Python setup, you might spend an hour debugging your .zshrc or shell aliases. With the Container-Only approach, we simply made the script smarter. We treated the input as “dirty data” and cleaned it.

The container acted as the Standardized Judge. If it didn’t work in the container, it was a logic error. If it did, the host’s quirks didn’t matter anymore.


The “Monstro” Execution

By mounting the current directory (pwd) to /app, we created a portal. The host machine provides the raw file, the container provides the processing power, and the result is spat back out onto the host.

1
2
3
4
docker run --rm \
  -v "$(pwd):/app" \
  python-runtime \
  python /app/resize-image.py data/photo.jpg data/small.jpg 50
This post is licensed under CC BY 4.0 by the author.