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”:
- Copy code into
Dockerfile. docker build.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
Pillowfor 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