Great experience working with this team. They completed the development work quickly and efficiently without wasting time. Communication was clear and consistent, they understood the requirements well, gave smart suggestions, and delivered exactly as discussed. I'd highly recommend them to anyone looking for a reliable, fast, and skilled app development team.
Case Study · Cloud Infrastructure & FinOps
Bounty Games: Budget-Conscious Cloud Infrastructure With Cross-Cloud Recovery
Bounty Games runs a mobile app, a public website and an admin panel on a single backend API. Mobilions designed and built its cost-optimized cloud infrastructure around a limited budget: production runs on ARM64 virtual machines in Oracle Cloud Infrastructure (OCI), Cloudflare handles the edge, and AWS provides independent monitoring, an offsite backup copy and a disaster-recovery environment that only runs when it is needed.

Project Overview
Bounty Games runs a mobile app, a public website and an admin panel on a single backend API. Mobilions designed and built its cost-optimized cloud infrastructure around a limited budget: production runs on ARM64 virtual machines in Oracle Cloud Infrastructure (OCI), Cloudflare handles the edge, and AWS provides independent monitoring, an offsite backup copy and a disaster-recovery environment that only runs when it is needed.
This is not a high-availability design, and we were careful never to call it one. Production has one application VM and one database primary. What the client gets instead is a set of recovery levels, each matched to a different kind of failure, at a running cost that covers only what is needed day to day. The platform is built to recover, not to never fail.
Client Snapshot
- Client
- Bounty Games (app.bountygames.org)
- Stage
- Budget-conscious, where every recurring charge gets questioned
- What they run
- A mobile app, a public website and an admin panel on one backend API
- Primary cloud
- OCI (ARM64 VMs running Docker); Cloudflare at the edge; AWS for monitoring, offsite backups and on-demand DR
- Non-negotiables
- Isolate production data, automate deployments with a way back, survive the loss of a cloud provider, rebuild any server from code, and keep the bill proportional to use
Services Delivered
Cloud infrastructure architecture and build
on OCI
with GitHub Actions, GHCR and health-gated deployment
with Terraform and cloud-init
Database backup and point-in-time recovery engineering
with pgBackRest
on AWS
Cloud cost optimization (FinOps)
Technology Stack
The Challenge
Bounty Games had a fixed, fairly small infrastructure budget and a short list of things they would not trade away. On a large budget, the default answer to "what happens if the database server dies?" is a second database server. On a small one, a second server running all month for a failure that might happen once a year is hard to justify. The job was to find cheaper answers that were still real answers.
The client set the priorities, roughly in this order: keep production data isolated from the internet and from development; make deployments repeatable and boring, with a way back if a release is bad; never be one cloud account away from losing the database; be able to rebuild any server from code rather than from memory; and keep the monthly bill proportional to actual use. The last point did not override the first four. It shaped how we delivered them.
Key engineering challenges
- One app server, no load balancer. Redundancy was not affordable, so the design relies on recovery instead of duplication.
- A private database that CI still has to migrate. The database has no public endpoint, so a GitHub-hosted runner cannot reach it to run migrations.
- ARM64 everywhere. ARM VMs were the cost-efficient choice on OCI and AWS, so every image had to be built for linux/arm64.
- Backups that outlive the provider. Backups in the same account as the database do not protect against losing the account, region or provider.
- Monitoring that does not share fate with production. A health checker in the same cloud can fail together with the thing it watches.
- Recovery that stays cheap while unused. A warm standby in another cloud is the textbook answer to provider loss, and also a second production bill.
Our Approach
The work is split across three providers, and each one is there for a specific reason. Cloudflare is the front door: every public hostname is a proxied DNS record, so users connect to Cloudflare's edge and Cloudflare connects to the origin. OCI runs everything users touch in normal operation. AWS holds only the pieces that must not fail together with OCI: a small always-on monitor, an offsite copy of the backups, and a disaster-recovery environment that exists as Terraform code until it is enabled.
The guiding idea is simple: match each failure to a recovery level, and pay for standby capacity only when something has gone wrong. Most of the cost decisions come down to one question, asked of every piece of capacity: does this need to run all the time, or only when something has broken?
Engineering Decisions
Two VMs, not one
Putting PostgreSQL on the application VM would save a VM. It would also mean a runaway container, a disk filled by logs or a bad deploy could take the database down with it. With the two separated, the app VM can be thrown away and recreated without touching data, and the database VM has its own rebuild and restore path. The application VM holds no state at all: data lives on the database VM, uploads go to Cloudflare R2, configuration comes from object storage, and secrets are written at deploy time. That is what makes it disposable.
ARM64 from CI to production to DR
Going ARM64 across GitHub runners, the OCI production VMs and the AWS DR instance means one image works everywhere. Building natively on GitHub's ARM runners also avoids slow QEMU emulation, the usual way to produce ARM images on x86 runners.
Migrations run on the server, not in CI
Because the database is private, giving a hosted runner a route to it would mean exposing the database or building a tunnel just for CI, which is exactly the exposure the private network exists to prevent. Instead, the deploy script runs Prisma migrations on the target VM over SSH, from inside the private network.
pgBackRest over scheduled dumps
A nightly pg_dump can lose up to a day of writes and restores slowly. pgBackRest works at the physical level, supports incremental backups, archives WAL continuously, and writes to repositories in two different clouds from one configuration. Managed PostgreSQL offers similar features, but at a price that did not fit this budget, and without a copy in another provider.
Cold DR, no Kubernetes, no load balancer
The disaster-recovery environment stays disabled behind a Terraform setting, so no DR compute runs until it is needed. Kubernetes comes up in every review of an architecture like this, so it deserves a direct answer: with one production application VM, there is nothing to schedule across, so a cluster would be paid-for features this deployment cannot use. The same reasoning ruled out a cloud load balancer in front of a single backend. Containers on a VM, plus self-healing and Terraform rebuilds, cover the same failure cases at this scale.
Technology Considerations
Terraform creates the machine; cloud-init makes it useful
Terraform is good at "this VM should exist with this shape" and poor at "install these packages in this order". Versioned rebuild scripts run by cloud-init are the reverse. Using each for what it does well keeps both readable during an incident, which is when you need to read them. Heavier configuration management would have added a tool to operate without changing what the system can do for a handful of VMs with clear roles.
Rollback restores the image, not the schema
Deployment is in-place container replacement with a health gate, not blue-green. Prisma applies forward migrations and has no automatic down-migration, so migrations have to stay backward-compatible with the previous release: add a column before code depends on it, remove it a release later. That is a team discipline, not something the tooling enforces.
Edge TLS is Full, not yet Full (strict)
Traffic between Cloudflare and Nginx is encrypted, but the origin certificate is not strictly validated. Moving to Full (strict) is listed as a planned improvement.
Implementation Highlights
Request routing and security boundaries
A production request resolves a hostname through Cloudflare, which terminates TLS at the edge and connects to the application VM's reserved public IP. Nginx is the only process listening for public HTTP and HTTPS; every container port is published on localhost only, so a misconfigured container cannot expose itself to the internet. The backend reaches PostgreSQL over the private network on port 5432, managed Redis for sessions and cache, and Cloudflare R2 for images. The AWS monitor deliberately checks the origin IP directly rather than through Cloudflare, so an edge problem is not mistaken for an origin failure.
CI/CD, deployment and rollback
The backend, admin and website each have their own workflow and deploy only their own container, so a website release cannot restart the API. Images are tagged by Git SHA, which makes rollback cheap: every image maps to exactly one commit and older tags stay in the registry, so rolling back means pulling an image that already exists. On deploy, a script writes the environment file, pulls the new image, runs migrations, recreates the container, runs a smoke test against the health endpoint, and keeps the new container only if the check passes.
Backups and point-in-time recovery
pgBackRest backs up the production database to two repositories, a primary copy in OCI Object Storage and an offsite copy in Amazon S3, both with continuous WAL archiving and scheduled full and incremental backups. Together these give point-in-time recovery: if a bad migration or an accidental delete damages data, the database can be restored to a point just before it happened. PostgreSQL's archive_timeout is set to 60 seconds so segments are archived regularly under low write traffic. This limits archiving lag; it does not guarantee a recovery point, which is why this case study does not quote an RPO.
Four recovery levels and a planned failback
Failures come in different sizes, and each one gets a matching response. The levels are kept deliberately distinct, and named honestly so engineers reading the architecture know exactly what each one does and does not do.
| Level | Failure | Response | Trigger |
|---|---|---|---|
| Level 1 | A container is unhealthy | autoheal restarts it; Nginx serves a maintenance page meanwhile | Automatic |
| Level 2 | The origin stops answering | AWS monitor switches DNS to an HTTPS maintenance page, and back on recovery | Automatic |
| Level 3 | An OCI VM no longer exists | Terraform recreates it; cloud-init rebuilds it from code | AWS monitor |
| Level 4 | OCI as a whole is unavailable | AWS DR environment enabled, database restored from S3, DNS moved to AWS | On demand |
| Failback | OCI is healthy again | Planned return to OCI after data synchronization | Planned |
Maintenance mode is not availability: while it is active the API, logins and admin panel are down, but users get a clear HTTPS page instead of timeouts while a lower level fixes the origin. Level 3 separates "unhealthy" from "gone" by checking Terraform's view of what exists against live state, so an overloaded server is not needlessly rebuilt. Level 4 only works because of two earlier decisions: backups in S3, and ARM64 images that run on AWS Graviton without a rebuild. One rule outranks any single command during DR: before DNS moves to AWS, the OCI database must be confirmed down, because two databases accepting writes at once is far worse than a longer outage.
Key Engineering Highlights
Where the budget was tight, the design spent it on recovery rather than on idle standby capacity. The cost decisions below nearly all avoid paying for capacity that sits unused, and each hands some responsibility back to the team.
Cost optimization decisions (FinOps)
| Decision | What it avoids paying for | What it adds |
|---|---|---|
| OCI ARM64 VMs as the primary cloud | Higher-cost compute for the same capacity | Every image must be built for linux/arm64 |
| Stateless app VM rebuilt from code | A permanently running standby app server | Rebuild time is downtime |
| Self-managed PostgreSQL + pgBackRest | Managed database pricing | The team owns backups, upgrades and restore testing |
| Backups in two clouds' object storage | A second live database in another provider | Recovery is a restore, not a promotion |
| DR behind a setting, off by default | An always-on second production environment | Activation takes a deliberate step and time |
| t4g.nano monitor, Cloudflare Free plan | A paid monitoring service and paid edge tiers | The team maintains the monitor |
| Docker on VMs, no Kubernetes | A cluster control plane and its overhead | No scheduling across nodes |
The honest tradeoffs are stated plainly rather than hidden:
Accepted trade-offs
| Trade-off | Accepted because | Risk it leaves |
|---|---|---|
| Single production app VM | No standby to pay for; Levels 1 and 3 cover most failures | A VM failure means downtime until restart or rebuild |
| Cold DR | No DR compute bill while unused | Slower activation; writes not yet in S3 are lost |
| Maintenance mode instead of failover capacity | A clear failure page at very low cost | The product is unavailable during an origin outage |
| Self-managed PostgreSQL | Cost control and backups in two clouds | Upgrades, tuning and restore testing fall on the team |
Outcome
Bounty Games runs on infrastructure sized for its budget without giving up what the client cared about most. Production data sits on a private database VM, archived continuously to two clouds and recoverable to a point in time. Deployments are automated, traceable to a commit and gated on health. Any VM can be rebuilt from Terraform and cloud-init, and a full OCI outage has a path to AWS that runs no compute until it is needed.
We deliberately do not publish monthly cost figures, savings percentages, a recovery point objective or a recovery-time figure. The value is in the decisions, not in numbers that depend on conditions at the time of a failure. The next step we recommend is not more infrastructure. It is restore drills: measure the real recovery time of each level and fold those numbers back into the runbooks.
What Clients Say
In their own words, Mobilions' clients describe fast delivery, clear communication, and senior, trustworthy engineering.
We worked with Ankit, Mayur, and Tushar to build the first version of Baba Hebrew for iOS and Android, and they delivered super fast. The team was responsive, reliable, and efficient, taking the idea from zero to a working app in record time. I'd recommend them to anyone who wants to get an MVP live quickly.
Tushar and Ankit did an outstanding job developing our native iOS (Swift) and Android (Kotlin) apps. They were efficient, responsive, and technically strong throughout. Thanks to their work, we launched successfully and gained over 1,000 users in the first 30 days. Highly recommend this team for quality mobile app development.
It was a wonderful experience working with Tushar, Ankit, and their team. They built a great mobile app for me and truly brought my vision to life. What stood out was not just their technical skill but their attitude: always positive, solution-oriented, and incredibly patient. They went above and beyond at every step, finding creative workarounds and staying committed even when things got challenging. Extremely professional and trustworthy. I would absolutely hire them again.
Frequently Asked Questions
No. Production has one application VM and one database primary. The design provides defined recovery paths, not redundant live capacity.
Checking through Cloudflare would test Cloudflare as well, and edge responses could hide an origin failure. Checking the origin IP answers the real question: is the production server healthy?
An HTTPS maintenance page served from AWS. The API, logins and admin panel are unavailable until the origin recovers and DNS is switched back.
On the target VM, during deployment. The runner connects to the VM over SSH, and the deploy script runs the migrations from inside the private network.
No. Rollback restores the previous container image. Applied migrations stay applied, which is why migrations must remain compatible with the previous release.
We do not quote one. Continuous WAL archiving keeps archiving lag low under normal operation, but the recoverable point is the last WAL segment that reached a repository, and that depends on conditions at the time of failure.
No DR compute runs while it is disabled. The always-on t4g.nano monitor and the S3 backup storage still incur costs.
We have not published a recovery-time figure. It depends on database size, restore speed from S3 and response time. Measuring it through restore drills is one of the recommended next steps.
To keep development cost low. It runs all services in containers behind Caddy, with its own database, Redis and secrets, and shares nothing with production.
Yes. A PostgreSQL streaming replica and a second application VM are the natural next steps if the budget allows. The existing backups, Terraform and CI/CD would carry over.
Ready to build cloud infrastructure like this?
If you need cloud infrastructure that fits a real budget and still recovers from the failures that matter, bring us the constraints you are working within.
Talk to Mobilions' Cloud & DevOps team about your architecture, recovery goals and cost targets.
Let's build your next big thing
Let's build your next big thing. Share your idea and get a free consultation. We respond within one business day.







