MY JOURNEY AS A DEVELOPER
Source: Dev.to
Level 1: Lunartech
The Beginning: The Experimentation Mindset
Hello readers, my name is Jim Amuto, and this is my story. One thing about me is that I love experimenting. In every project I do, I don’t follow the norm. I ask myself:
- What am I doing?
- What is the end goal?
- How much faster can I ship it to actual users or increase my workflow speed?
That curiosity turned into a passion for software development, especially DevOps. I’m grateful for my current position because it let me spot loopholes and the need to ship code faster.
The Challenge: Full‑Stack in an AI Startup
I remember being on a deadline with a mountain of tasks. In an AI startup you’re often required to wear many hats—full‑stack roles instead of a narrow specialization. That’s great for becoming a “jack of all trades” and later focusing on a niche as you grow.
Shipping Code Under Pressure
My big project was an AI translator that had to handle long‑form content—something my boss insisted on. The device I was using became a bottleneck; AI/ML workloads are computationally intensive, but there are no excuses. I tried repeatedly until it failed.
I reached out to a friend with a more powerful machine. We hopped on a call, he cloned the repo, and I told him what to install first. He ran a dependency‑install script and—literally—dependency hell erupted. He fixed what he could, but the build still failed because of a maze of specific version requirements. I eventually told my boss that my PC wasn’t enough.
I researched cloud computing and found many GPU/CPU rental options, but I didn’t have the budget. I suggested the idea to my boss and waited for feedback—a painfully long wait.
The Revelation: Why Docker?
Why am I telling you this? I had a revelation: if my code couldn’t run on my friend’s setup, how could I expect it to run in production?
Time was ticking. I had multiple Jira tickets, and two months in I still hadn’t completed them. Some tasks—like building a simple progress tracker—looked easy on paper but stalled because of stack incompatibilities.
At that point I accepted that the frontend could be harder than the backend. A broken progress tracker is non‑negotiable; no user wants to see it. The pressure to finish was intense, and I almost gave up.
Then I thought: maybe my approach is wrong. I dug deeper, researched, and decided to try Docker to package my code.
Docker is a tool that packages everything your code needs into an image that can spin up isolated, ephemeral container instances.
The Docker Gamble
Docker was a big gamble for me. It took hours to implement and came with many reality checks:
- Long build times.
- My AI coding agent (the “assistant”) kept performing actions I wouldn’t attempt now, erasing progress and forcing me to backtrack.
I sat down, planned with the agent, and asked:
- Why rebuild this and not that?
- When should I clear the cache and volumes?
These questions saved me hours that I’d previously wasted restarting containers instead of rebuilding them to capture new dependencies.
I enabled hot‑reload by binding the host code directory to the services (backend and frontend). The containers could instantly see my changes without a rebuild—a game‑changer that sped up my workflow while keeping the environment isolated.
The Persistence Challenge
Everything was smooth until I needed the application to survive restarts. Docker containers keep commands running continuously, so I didn’t have to restart a terminal to see changes, but the frontend still needed to reflect those changes.
My stack includes:
- Next.js – frontend
- FastAPI – backend
- Numerous model dependencies
- Ollama – local LLM serving
The Ollama Experiment
Why Ollama? I jumped on the hype train that local models protect data, have no rate‑limiting, and are free to query.
I was building a translator. Using external APIs gave literal, context‑less translations. I scoured the internet for free models—painful, but that’s the trade‑off.
My boss sent me a link to TranslateGemma, an open‑source Google model. It was perfect at the time, and a pre‑built Ollama image existed, so I ran:
ollama pull translategemma:4b
My system couldn’t handle the 12B/27B parameter versions, but I was committed. I set up an Ollama container and everything worked for a few days.
When I tested long‑running content, Ollama ate all the memory allocated to the container, causing the resources to run out. I switched to running Ollama locally.
Question: If it consumes that much memory, is it viable in production? The answer implied high costs—something I kept in the back of my mind.
Enter Redis: The Swiss Army Knife
My project has two login methods:
- Demo login – for fast prototyping (boss insisted).
- Auth login – production‑grade authentication.
I built the demo login using local storage, which was quick but not secure. To unify the two flows and add a caching layer for session data, I introduced Redis. It became the “Swiss Army knife” of my stack, handling:
- Session storage
- Rate‑limiting for API calls
- Simple key‑value caching for model outputs
Redis allowed the demo and auth logins to share a common session store, making the transition from prototype to production smoother.
Takeaways
- Never assume your dev machine reflects production.
- Docker isolates dependencies and eliminates “it works on my machine” headaches.
- Hot‑reload via bind mounts dramatically speeds up iteration.
- Local LLM serving (Ollama) is great for data privacy but can be memory‑hungry; always benchmark for production.
- Redis is an underrated tool for session management and lightweight caching in a full‑stack AI app.
My journey from dependency hell to a production‑grade setup taught me that the right tooling—and a willingness to experiment—can turn impossible deadlines into manageable milestones.
In Memory
I set aside the auth login for later and spent most of my time in demo mode so that whenever my boss requested progress, I could provide it immediately.
However, storing outputs in local memory contradicted the requirement for the app to survive restarts because local memory wouldn’t work in production. So I had to find a solution.
That’s when I discovered Redis, a small, lightweight instance with many uses—from caching to persistent storage. It was very fast. At first, I used it as storage for my demo jobs since I wasn’t using a database, and it worked perfectly.
Unlocking Redis’ Potential
Later, I realized I wasn’t using Redis to its full potential. I had my backend send APIs to Redis to query task progress, and the frontend fetched progress from the backend—but that failed.
I tried using WebSockets to update progress, and it worked partially. I was excited, especially since I needed progress bars for five tasks.
The Final Piece: Celery
I had often heard that Redis and Celery go hand in hand. I used to wonder what RabbitMQ and Kafka were. It turns out they are message brokers. Once again, my superhero Redis could act as both a message broker and a task queue—that’s four roles.
Celery, as I understand it, is the worker that:
- Offloads tasks from the task queue.
- Processes them.
- Sends a completion message to the broker to notify the frontend to fetch results.
It took me hours to implement, but this was genuinely one of the best things I have ever done. It fixed so many issues. I realized FastAPI was part of the problem—it couldn’t handle many concurrent tasks asynchronously the way I needed. That’s why I never got proper updates before.
Now all my progress trackers work, and all I have left to do is make the progress bars look uniform.
To be continued…