Technicality
- Low Does not need any software engineering knowledge.
- Intermediate Assumes you know software engineering principles, but knowledge about a specific technology is not required.
- Specific The topic is in-depth about a specific technology.
The Reasoning and Decision Making Behind 10-20.io's Cloud Infrastructure
10-20 is a location sharing and encrypted communications platform allowing first responders to use their phone instead of an expensive fake-radios (most "radios" are LTE phones that look like radios), saving them tens of thousands of dollars a year. Here is the infrastructure powering that platform, and the reasoning behind my decisions.
10-20, which is police code for "location requested," is a location-sharing and encrypted communication platform serving first responders. At the time I'm writing this, we have 19 police and fire departments, primarily in the Fort Worth and Waco, Texas, areas.
The app continues to grow, becoming bigger and better in a continuous iterative cycle of improvement. We're not done yet, but I want to talk about where 10-20 was and where it is now, and discuss the reasoning and decision-making behind its cloud infrastructure.
Why? I love the history of companies. I find it incredibly fascinating learning the "why" behind decisions and the context that formed them. One of my favorite books is The Design and Evolution of C++ by Bjarne Stroustrup (1994). Read this one sentence on the back, and you'll see why it's exactly what I like: "Without omitting critical details or getting bogged down in technicalities, Stroustrup presents his unique insights into the decisions that shaped C++".
In a way, this article is my version of that, but in no way am I suggesting 10-20 is more important than C++ the language. I view this article merely as a case study - a diary of my decision making so I can look back at the flaws and try to learn to avoid them in the future.
If you do not care about the history of 10-20's infrastructure, skip to the last section where I detail the current infrastructure.
Version 1.0: The 3 Months Before Me
I joined 10-20 in March of 2025. Glen Bucy, co-founder and CEO, learned to code to create the first version of 10-20. It was a vanilla, single-file PHP website on a simple EC2 instance with the database hosted on the same instance. It worked for what we needed at the time: a simple, location sharing app where mobile apps sent POST requests to the server, and the website retrieved locations by simply polling the server every X seconds.
Everything was one file that was under 300 lines of code. Styling was done with Bootstrap. To run a "deployment", you simply copy the file from your computer and paste it into the VM.
You can really see its simplicity. It's a single point of failure, it doesn't scale, backups have to be done manually on the VM, deployments are prone to human error, compliance issues, no version control.
What's ironic is most of those didn't matter at the time. We are a small startup with no revenue. 10-20 at this time only had a dozen or so users. It's okay things don't scale. The product was in its infancy, only being used and value determined by about a dozen users.
What makes me the most nervous about this setup isn't the performance or scaling: it's the human errors.
Doing CLI commands manually by shutting down the prod VM. Doing a deployment by copying a file from your computer. I get the mantra of "do things that don't scale" and I agree with it (see my statements above), but in 2025 implementing basic DevOps is not a big lift for an experienced software team. In fact, the benefits of such a small change are fundamental and a must-have.
Version 2.0: Rebuilding on a Good Foundation
There are smarter people than me in this world, and those smart people make frameworks so that we can focus on app code and not worry about things like security defaults, structure, database migrations, etc. There's a learning curve, but the payoff is huge.
I was laid off from the FBI in Q2 2025. I decided to take a hiatus and work on 10-20 full time, allowing me to put some dedicated time into the rewrite.
Choosing the Tech Stack
So … what do we swap to? Next.js? Laravel? Go? Express? Django?
Not just in 10-20, but everywhere, two questions drive my tech stack decisions more than anything else:
- What do the developers know?
- Will the performance make a difference the user can actually feel?
What do the developers already know?
I've worked in a lot of different technologies, but JavaScript is my home turf. I've always done web development of some kind, either in React or Node. Meanwhile, Glen only knows PHP, and he isn't a professional programmer. He learned to code to build the first version of 10-20.
If we picked a tech stack with JavaScript, Glen would have to learn a new language and the concepts behind an MVC framework at the same time. If we picked PHP, I'd only have to learn a new language since I already knew how MVC frameworks work. One of us learning one thing is cheaper than the other learning two.
So that narrows it down to PHP, and PHP has one dominant framework: Laravel. Laravel checks all the above boxes with authentication, queues, database migrations, and a real project structure out of the box to help us in our coding with and without AI (heavily opinionated frameworks are much better for AI).
Will the performance make a difference the user can actually feel?
PHP is famously slow. Admittedly at the time, I did not know that PHP was just about the slowest thing you could pick. In the default setup of Laravel, when web requests are received, the entire framework boots from scratch on every request before a response is sent. But two things mattered more:
- Will the user even know? Even though PHP is slow, will the user even notice? Our users will not notice if an update occurs within 2 milliseconds or 200.
- Is it worth the work? Most startups fail. Time is better spent delivering value to customers and features than optimizing performance for traffic we may never have.
On the backend, PHP's slowness is a matter of milliseconds. When an officer's phone sends its location, it doesn't matter whether the server takes 2 ms or 200 ms to process it. Nobody watching the map or using the website can tell the difference.
But the frontend is a different story. If PHP rendered the map, the server would build the whole page and send it to the user, and the only way to show an officer's new position would be to build and send the page again. That means a refresh. The screen will flash, the user has to do something, the map resets, and you lose whatever you zoomed in on. Now that's something the user notices every few seconds.
So we have to split it. For the backend, PHP is totally fine because the overhead doesn't matter, but we need to figure something out on the frontend. The same two questions can be asked on what tech stack to use for the frontend, and since I'm a React guy, it's React.
However, not every part of the app feels the same way. A one-time page load can absorb a few extra milliseconds without anyone noticing. However, we're location-sharing. We have a live map where dozens of officers' positions are being sent and fetched every few seconds.
Remember, this is in Q1 2025. In hindsight, we will quickly come up to the limits of PHP web performance as more users join 10-20.
Choosing Azure and PaaS
With this next section, you'll start to notice a trend. I value the time to develop above all.
The choice of either doing AWS or Azure was simple: I know Azure. The value I bring from my career is utilizing Azure and not AWS. As 10-20 CTO, I need all the knowledge I can get. Azure (Azure GCC, Government Community Cloud) is the clear path forward to maximize my knowledge.
I think modern computers are so incredibly fast that the inefficiencies of a language don't matter in most applications. I apply the same mindset to cloud infrastructure.
There's a sarcastic quote I live by: "Hiring a senior solutions architect for $180K/yr can save you $400/mo in cloud costs."
Sure, running a VM is cost-optimized and gives you maximum control. But at what cost? You have to manage that. You have to build the pipelines that deploy that. You have to set up that scaling. You have to do all this extra stuff just to keep the lights on. With PaaS, for $60 a month for an App Service instance (equivalent of AWS Elastic Beanstalk), you serve it a Docker image and set up the environment variables, and it runs. If the app needs to scale, you have peace of mind if you have a large influx of customers, which, the way 10-20 operates, is when our customer is a police department with tens to even sometimes hundreds of users. That peace of mind is worth $60 a month.
What We Built
10-20 2.0 launched in Q3 2025: a Laravel backend and a React frontend, running in Azure Gov.
The app ran as three separate services: the Laravel web server, a queue worker for background jobs, and Laravel Reverb for WebSocket connections. Reverb is what made the live map work the way I described earlier. Instead of the map asking the server for updates every few seconds, the server pushes each new position to the map the moment it arrives.
Everything was defined as infrastructure-as-code with Azure Bicep and deployed through GitHub Actions to three environments: production, staging, and development.
Remember what made me most nervous about 1.0: the human errors. Copying files onto a production VM, running manual commands on the live server. In 2.0, none of that happened anymore. Every change to the app or the infrastructure went through the same pipeline, the same way, every time. That's what let us build features fast without worrying about breaking production.
This gave us exactly what I wanted: reliability in both app code and infrastructure so that we could build features fast.
Version 2.0 to 3.0: Performance Hits and 10-20 Comms
As time went on and we entered Q4 2025 and Q1 2026, more police and fire departments started joining 10-20. We started seeing noticeable performance issues, particularly with web requests. As I hinted at earlier, we were starting to see the issues of PHP handling live update web requests. When a web request is received, the framework evaluates all the middleware, all the controllers, literally everything before it is sent back. This isn't a problem for normal web viewing, but it did bog down the response when viewing the map, which is supposed to show live updates.
At only about 100 users transmitting their location, we needed a beefy P3V3 server on Azure App Service. "Do things that don't scale" has a limit if it bankrupts the company.
At this time, we were also getting a lot of requests and interest in a communications platform.
Existing apps, such as Microsoft Teams, Slack, etc., are general-purpose apps and are not made for first responders. As we learned from our users, these existing apps required a hefty onboarding. In-house police IT had to set up this software, which sometimes required loads of config. Then once you're in, it was overly complicated. Slack and Teams have tons of extensions and integrations, but all that is complicated for users, especially folks that are not already adept at technology. And the general-purpose optimizations that these apps make, such as caching so that all messages aren't fetched on every request, subject officers to being subpoenaed. Because these are small departments, a lot of these departments use their personal phones, meaning that if you talk about or send pictures on a case, a judge could subpoena it, or a FOIA request could cause users' personal phones to be taken away for days on end. Super annoying.
So, there are two things at hand:
- A communications platform sounds like it could be viable.
- Performance is starting to take a hit.
We got to work for version 3.0 and the release of 10-20 Comms. With it came some other major changes as you would expect from a complete product launch, but we'll be ignoring all app code and table changes.
You'll notice two fewer resource groups.
Earlier in the year, we had PRs automatically build a sandbox environment and then get destroyed when the PR closed. I was really proud of this, but it ended up being unmaintainable as the interactions with the mobile clients became increasingly difficult to hook up to a dev environment. It was just me and Glen, and I overengineered it. We really did not need the added complexity just to hook up to a sandbox environment. Other changes that didn't require mobile clients to connect could just be done locally very easily by just pulling down the branch and running it locally.
It was also really expensive. As we worked on 10-20, it was clear our DevOps process was reliable enough that major outages wouldn't happen on deployment. It doesn't mean we don't test. It just means we test locally. Saving the money of duplicate environments is worth the risk of deployments going from local to prod.
Also added to the infrastructure was a storage account. The storage account is where the messages and images go.
Swapping from GitHub to GitLab, a Rant
Probably even more controversial than React vs. Vue is my decision to go from GitHub to GitLab. Though there are some objective benefits of GitLab over GitHub, such as not having to have label-hell because GH calls everything an "issue" and just lacks any proper project management tooling, the majority of the decision to move from GitHub to GitLab is subjective and a personal preference.
- I like the UI of GitLab better. Sorry, not sorry.
- Project management feels more complete. It has proper incident management, epic tracking, and work items that are properly named and identified between incidents, epics, and work items instead of everything being an "issue". When you work with GH issues, it's just real sloppy labels that have to go on everything to mimic a fraction of what GitLab does out of the box with a better UI.
- GitLab pipelines natively support staging and do not require wonky chaining to mimic the behavior, as well as offer deployment management and approvals.
Let me crash out: "Pull request" is dumb. I don't care what the history is. You are not requesting to pull. You are requesting to merge. It's a merge request. "Change list" is also acceptable. Also, the word "issue" is a relic of the past and GitHub needs to let it die and copy GitLab and ADO and call it a "work item". Ugh.
Today's 10-20 Architecture
As we added authentication to the app, MFA to the app user login, emails, texting, image uploads for messages, and much more, the web server slowly started becoming bogged down because of PHP overhead when loading web requests. This is a very hard problem to solve simply because I'm not an expert in Laravel.
Laravel had a built-in solution: Laravel Octane. Octane caches the web response to a configurable amount of web requests before the cache is reset. Because of the position updates, this happens fairly frequently. You can actually see the spikes in the application CPU usage every 5 minutes for re-caching the web responses for Octane.
The Biggest Infrastructure Problem of 10-20
I still live by the mantra of the time to develop being the most critical factor, and the dollar cost for PaaS and using a framework being well worth it. But "do things that don't scale" comes with a good problem: if you grow, there are growing pains. Right now that biggest pain is…
Both the queue and positions tables are on the same database server.
I've thought about making the positions API and/or the queue not be Laravel/PHP and use something more performant like Go, but when I looked into it more closely, it turns out that the main bottleneck is not actually the execution of PHP but rather the database's CPU usage.
The queue only sometimes has delays because the positions API is using the same table as the queue jobs. One of the two should probably not be on the same server competing for the same CPU usage. I'm thinking that the positions API and its table should be their own app container running separately from the main database server and thus the queue (because again, I have found that the slowness of PHP does not matter most of the time).
But again, that database server only struggles because it has one core.
If we have that many users, will the couple hundred a month for a two-core database be worth more than taking away from feature and bug fix time to make a dedicated positions API? I don't know.