Jacob Wiseberg

← back

Containerizing My Side Projects With Docker and a Spare AWS Instance

I had three or four small side projects running directly on the Beelink with no isolation between them, and it finally bit me when updating one project’s dependencies quietly broke another that happened to share a Python environment.

The mess I’d built

Each project had grown organically: one was a Flask app, one a static site generator, one a scheduled script, all installed into whatever the system Python happened to be at the time, with no record of which version of anything each one actually needed. Reproducing the setup on a fresh machine would have meant archaeology, not documentation.

Containerizing one at a time

Rather than a big-bang rewrite, I containerized them one at a time, starting with the one that broke:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]

Trivial on paper, but writing it forced me to actually enumerate what each project depended on instead of relying on whatever had accumulated in the system environment over time. Two of the three had dependency conflicts with each other that I’d simply never noticed because they’d never been forced to coexist explicitly.

Docker Compose tied them together with their own isolated networks:

services:
  dashboard:
    build: ./dashboard
    ports: ["8080:8080"]
    restart: unless-stopped
  scheduler:
    build: ./scheduler
    restart: unless-stopped

restart: unless-stopped alone fixed a recurring annoyance: previously, if the Beelink rebooted after a power blip, I’d come back to half my projects silently not running until I noticed and restarted them manually.

Why one project went to AWS instead

Everything stayed local except one: a small public-facing tool that occasionally needed to be reachable without my home network being involved. Running it on a spare t4g.micro instance meant I didn’t need to think about port forwarding, dynamic DNS, or my home IP changing.

docker context create aws-box --docker "host=ssh://ec2-user@my-instance-ip"
docker --context aws-box compose up -d

Docker contexts turned out to be the detail that made this painless. I can build and test locally against the exact same Compose file, then point the same docker compose up at the remote context when I’m ready to deploy, without maintaining a separate deployment script or copying files around manually.

What surprised me

The ARM-based instance meant a couple of base images without multi-arch builds failed outright, which I hadn’t thought about until the first deploy errored out. Rebuilding with --platform linux/arm64 explicitly specified, and switching one image to a maintained multi-arch alternative, fixed it. It’s a small thing, but it’s exactly the kind of gotcha you only learn by actually deploying to ARM once instead of reading that it’s “basically the same as x86.”

Where this leaves things

Four small projects, each with an honest, reproducible definition of what they need, isolated from each other, with one living on a tiny cloud instance instead of my home network. None of this was complicated technology. It was entirely about finally being disciplined with boundaries I’d let blur for too long, and Docker was just the forcing function that made the boundaries explicit.