~/potatohd.ruRU
← All posts
Jul 16, 2026·12 min read

Smalltech: what bigtech-grade infrastructure looks like when one person builds it

architectureDokploysmalltech

Every post on this blog so far has been about a product: the dating app, the real-estate aggregator, the Discord-compatible chat server, Deployka itself. Each one got its own write-up, its own architecture, its own set of decisions. None of them said much about what any of it actually runs on.

Underneath all of it sits the same stack, carrying roughly 40 services across the fleet: one identity provider, one billing engine, one observability platform rolled out further than it's actually used, one shared analytics project, one edge proxy sitting in front of every internal dashboard, a homegrown SMS gateway built on a consumer 4G router, seven storage engines each picked for the workload it actually fits. None of it is unusual at bigtech scale, where a platform team runs this toolkit full time. Here it's one person, building the products on top of it and operating the floor underneath at once. I call the whole approach smalltech: bigtech-shaped infrastructure investment, built and run by one person instead of a platform team.

#Dokploy

The whole fleet deploys as Docker Compose stacks, pulled straight from git repositories by Dokploy, which organizes it all into projects and environments. Dokploy itself is self-hosted and open source. The rule behind it holds with no exceptions: infrastructure changes happen through git. Commit, push, redeploy. Nothing gets hand-edited on a running server — if a fix isn't in the repository, it isn't a fix yet.

One gotcha took a while to trust. Dokploy's own redeploy button and the API behind it don't always do what they say for a git-sourced app: sometimes redeploying doesn't pull anything new at all, and instead just replays a copy of the compose file Dokploy already had cached in its own database, then reports success regardless. You notice it the boring way: a fix you just shipped is somehow still broken, or a feature that should be live still isn't there, while the deploy log insists everything went out clean.

Once the failure mode is known, the fix is straightforward: skip the redeploy button entirely and hit the application's real deploy webhook URL directly, with a push-shaped payload. That forces Dokploy to treat the call like a genuine incoming push and actually re-clone the repository, instead of trusting whatever it already has sitting in its database.

#ZITADEL handles login

Nobody in this fleet writes their own login system. ZITADEL, self-hosted and open source, is the single sign-on and OAuth/OIDC layer behind nearly all of it: the dating app, the real-estate aggregator, the education platform, the Discord-compatible chat server, Deployka itself, and pretty much everything else. Every product that plugs into it gets the same login for free, instead of building and maintaining its own.

#FlexPrice counts the money

Money works the same way. One FlexPrice deployment, self-hosted and open source, handles subscriptions, usage-based billing, and payments for six different products at once: Deployka, the transcription SaaS, the dating app, the fitness and booking platform, one of the e-commerce brands, and the Discord-compatible chat server's premium tier. Those six don't share much of a business model otherwise, and FlexPrice covers all of it without a separate billing stack built for each one. Every new product just plugs into subscription logic that already exists, already works, and already survived contact with a previous product's traffic.

#Observability and SigNoz

Out of roughly 40 services in the fleet, about 7 or 8 actually ship real telemetry to SigNoz on any given day — traces, logs, and metrics, the three signals it's built to hold. SigNoz itself is self-hosted and open source, rolled out across the whole fleet. Instrumentation code sits in a lot more services than the ones whose data actually shows up when you go looking for it.

That gap is worth sitting with, because it isn't really a SigNoz problem. Standing up the platform once, fleet-wide, is a single piece of work: one deployment, one place for everything to point at. Getting each individual service to actually emit good telemetry is a different kind of work entirely — smaller, less urgent-feeling, and it has to happen once per service, for as long as the fleet keeps growing. Against a backlog of feature work and actual user-facing bugs, wiring up tracing for a service that's currently working fine loses that argument almost every time, because nothing forces the issue. A broken feature announces itself. A service that's quietly not sending telemetry doesn't announce anything at all — it looks exactly as finished as everything else from the outside, while the real visibility into it stays wherever it happened to stop.

That's the real cost of the gap: for the other three dozen or so services in the fleet, a failure that never reaches a user directly has no dashboard that would show it. It would have to surface some other way — a user complaint, or a coincidence while doing something unrelated — before it's visible at all.

#PostHog for analytics

PostHog runs a single project for the entire fleet. Every event carries an app property, so sharing the space costs nothing: a signup in the dating app and a signup in the booking platform land in the same project and stay perfectly distinguishable. One dashboard covers every product at once, and comparing them against each other is a matter of filtering by that property. One analytics setup that scales by adding a value beats standing up a separate one per product.

#Pangolin at the edge

Pangolin sits in front of most of the fleet's internal web UIs — dashboards, admin panels, anything not meant for the open internet — as a self-hosted reverse proxy with SSO built in. An unauthenticated request gets bounced to a login page before it ever reaches the app behind it, which means none of those internal UIs needs its own login system either: Pangolin is already standing in front of all of them.

Managing it day to day has a quirk built in. The obvious path for adding or changing a route would be Pangolin's own management API, but there's no working non-interactive way to drive it: no API key that works for it, no CLI either. So the real workflow skips the API entirely. Adding or changing a route means writing directly into Pangolin's own database, by hand, carefully, since nothing on that path checks the write for you.

That sounds slower than it is. There's no static config file to edit afterward and no reload command to run: the reverse-proxy layer sitting in front of Pangolin polls that same database continuously, in real time, and picks up whatever changed. A route written straight into the database is live in about five seconds. The database is the configuration; there's nothing else downstream of it to keep in sync.

#A homegrown SMS gateway

SMS sending runs through a real, physical consumer 4G router sitting on my own network at home — a TP-Link Deco X20-4G — driven by a custom Go service that speaks the router's own local and cloud control protocols directly. No third-party SMS API provider sits anywhere in the chain. Nothing about this one is abstracted behind someone else's API: it's a physical box at home, and the service driving it has to speak its exact dialect or nothing sends at all.

None of it worked out of the box. The first real lead was the router's own firmware: extracting it and pulling it apart turned up an internal API path built for sending SMS, sitting right there in the code the router runs. That looked like the whole problem, solved in one step, until it wasn't. The API only answered to a session the branded phone app itself was holding, with no way to present that session from outside the app. Extracting the firmware got the map. It didn't get the key.

The path that actually worked went around the router entirely and through the vendor's cloud instead, the same route the phone app itself uses. That meant intercepting the app's own network traffic and rebuilding the proprietary binary protocol underneath it, piece by piece, until the same sequence could be triggered from outside the app entirely. The gateway that came out of it sends real messages today, wired into two places: ZITADEL, for OTP and verification codes, and the notifications platform.

#A database for each job

Seven storage engines run under the platform: a shared Postgres carrying dozens of services on its own, ClickHouse for columnar and analytics workloads, Redis, MariaDB, Neo4j, Qdrant for vector search, Manticore for full-text search. Nothing here got forced into one database just because it was already there — each workload sits on the engine actually built for the shape of its data and its queries.

There's a security migration running underneath all of that right now, separate from picking the right engine. For most of this platform's history, any service that needed to reach Postgres, or any of the other six, used the same one shared superuser credential every other service also used. That's convenient right up until it isn't: a bug or a leak in one service's connection string is, in principle, a bug or a leak into every other service's data sitting on that same engine, plus whatever administrative power that superuser account carries beyond ordinary reads and writes. The migration replaces that one shared login, engine by engine, with individual credentials scoped to exactly what each service needs and nothing beyond it. It's real engineering work: each of the seven engines handles permissions its own way, so it multiplies into seven separate migrations running in parallel. It isn't finished everywhere yet.

#Bottom line

Running a stack shaped like this, alone, buys real things: control over your own data and infrastructure, no vendor lock-in, no per-seat SaaS pricing that climbs faster than the business underneath it, and genuine end-to-end understanding of every layer, because the same person built every layer and now operates it. When something breaks, there's no vendor ticket to file and wait on — whoever wrote the thing that broke is already looking at it.

It costs something too, and the cost is symmetrical with the benefit. The same one person who understands every layer is also the only one who can be paged for any of them, at any hour: identity, billing, observability, analytics, the edge proxy, the SMS gateway, seven different databases, with no rotation to hand a 3am incident to. The SigNoz gap from earlier in this post is a concrete, currently active example of exactly where that debt is visible right now — services running today with no telemetry behind them, on a platform built specifically to hold it.

// available for hire

Need a website or setup, done right?